diff --git a/.gitignore b/.gitignore index 8f6fc3c8dd..e3764d839f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ jitwatch.out .settings .project .gradle +.kotlin .vscode .idea *.iml diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java index 3d5d2314f2..20a71a3da1 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java @@ -3163,6 +3163,7 @@ T expireAfterAccessOrder(boolean oldest, Function<@Nullable V, @Nullable V> * @param mappingFunction the mapping function to compute a value * @return the computed value */ + @SuppressWarnings("NullAway") T snapshot(Iterable> iterable, Function<@Nullable V, @Nullable V> transformer, Function>, T> mappingFunction) { requireNonNull(mappingFunction); @@ -4009,6 +4010,7 @@ static final class BoundedPolicy implements Policy { @Override public @Nullable V getIfPresentQuietly(K key) { return transformer.apply(cache.getIfPresentQuietly(key)); } + @SuppressWarnings("NullAway") @Override public @Nullable CacheEntry getEntryIfPresentQuietly(K key) { Node node = cache.data.get(cache.nodeFactory.newLookupKey(key)); return (node == null) ? null : cache.nodeToCacheEntry(node, transformer); 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 57dd96cb68..7f6968ca19 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 @@ -28,6 +28,7 @@ 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.Objects.requireNonNull; import static java.util.function.Function.identity; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -292,8 +293,8 @@ public void put_insert(AsyncCache cache, CacheContext context) { public void put_replace_sameValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { - var oldValue = cache.asMap().get(key); - var value = cache.asMap().get(key).thenApply(val -> intern(new Int(val))); + var oldValue = requireNonNull(cache.asMap().get(key)); + var value = oldValue.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)); @@ -307,7 +308,7 @@ public void put_replace_sameValue(AsyncCache cache, CacheContext conte @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }) public void put_replace_sameInstance(AsyncCache cache, CacheContext context) { for (Int key : context.firstMiddleLastKeys()) { - var value = cache.asMap().get(key); + var value = requireNonNull(cache.asMap().get(key)); assertThat(cache.asMap().put(key, value)).isSameInstanceAs(value); assertThat(cache).containsEntry(key, value); } @@ -435,7 +436,7 @@ public void putIfAbsent_nullKeyAndValue(AsyncCache cache, CacheContext removalListener = { Listener.DISABLED, Listener.REJECTING }) public void putIfAbsent_present(AsyncCache cache, CacheContext context) { for (Int key : context.firstMiddleLastKeys()) { - var value = cache.asMap().get(key); + var value = requireNonNull(cache.asMap().get(key)); assertThat(cache.asMap().putIfAbsent(key, key.asFuture())).isEqualTo(value); assertThat(cache).containsEntry(key, value); } @@ -589,8 +590,8 @@ public void replace_failure(AsyncCache cache, CacheContext context) { public void replace_sameValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { - var oldValue = cache.asMap().get(key); - var newValue = cache.asMap().get(key).thenApply(val -> intern(new Int(val))); + var oldValue = requireNonNull(cache.asMap().get(key)); + var newValue = oldValue.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)); @@ -718,8 +719,8 @@ public void replaceConditionally_wrongOldValue(AsyncCache cache, Cache public void replaceConditionally_sameValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { - var oldValue = cache.asMap().get(key); - var newValue = cache.asMap().get(key).thenApply(val -> intern(new Int(val))); + var oldValue = requireNonNull(cache.asMap().get(key)); + var newValue = oldValue.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)); @@ -1310,7 +1311,7 @@ public void merge_absent(AsyncCache cache, CacheContext context) { public void merge_sameValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap>(); for (Int key : context.firstMiddleLastKeys()) { - var value = cache.asMap().get(key).thenApply(Int::new); + var value = requireNonNull(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); @@ -1566,7 +1567,9 @@ public void keySet_removeAll_partial(AsyncCache cache, CacheContext co 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(); + .contains(Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); } @CheckNoStats @@ -1739,7 +1742,9 @@ public void keySet_retainAll_partial(AsyncCache cache, CacheContext co 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(); + .contains(Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); } @CheckNoStats @@ -1872,6 +1877,7 @@ public void keySpliterator_estimateSize(AsyncCache cache, CacheContext /* ---------------- Values -------------- */ @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void values_toArray_null(AsyncCache cache, CacheContext context) { @@ -1978,12 +1984,15 @@ public void values_removeAll_none_populated(AsyncCache cache, CacheCon 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); + var removed = Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(cache.asMap().get(key))); 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(); + .contains(Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); } @CheckNoStats @@ -2026,7 +2035,7 @@ public void values_remove_none(AsyncCache cache, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL) public void values_remove(AsyncCache cache, CacheContext context) { - var future = cache.asMap().get(context.firstKey()); + var future = requireNonNull(cache.asMap().get(context.firstKey())); assertThat(cache.asMap().values().remove(future)).isTrue(); var expected = new HashMap<>(context.original()); expected.remove(context.firstKey()); @@ -2167,7 +2176,9 @@ public void values_retainAll_partial(AsyncCache cache, CacheContext co 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(); + .contains(Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); } @CheckNoStats @@ -2438,14 +2449,17 @@ public void entrySet_removeAll_none_populated(AsyncCache cache, CacheC @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 removed = Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(cache.asMap().get(key))); 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(); + .contains(Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); } @CheckNoStats @@ -2656,7 +2670,9 @@ public void entrySet_retainAll_partial(AsyncCache cache, CacheContext 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(); + .contains(Maps.asMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); } @CheckNoStats 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 908b1b6157..c66fd42a0b 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 @@ -29,6 +29,7 @@ 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.Objects.requireNonNull; import static java.util.function.Function.identity; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -85,6 +86,7 @@ public final class AsyncCacheTest { /* --------------- getIfPresent --------------- */ + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getIfPresent_nullKey(AsyncCache cache, CacheContext context) { @@ -111,12 +113,14 @@ public void getIfPresent_present(AsyncCache cache, CacheContext contex /* --------------- getFunc --------------- */ @CacheSpec + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") public void getFunc_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> cache.get(null, key -> null)); } @CacheSpec + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") public void getFunc_nullLoader(AsyncCache cache, CacheContext context) { Function mappingFunction = null; @@ -124,6 +128,7 @@ public void getFunc_nullLoader(AsyncCache cache, CacheContext context) } @CacheSpec + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") public void getFunc_nullKeyAndLoader(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> cache.get(null, (Function) null)); @@ -133,6 +138,7 @@ public void getFunc_nullKeyAndLoader(AsyncCache cache, CacheContext co @Test(dataProvider = "caches") public void getFunc_absent_null(AsyncCache cache, CacheContext context) { Int key = context.absentKey(); + @SuppressWarnings("NullAway") var valueFuture = cache.get(key, k -> null); assertThat(context).stats().hits(0).misses(1).success(0).failures(1); @@ -146,6 +152,7 @@ public void getFunc_absent_null_async(AsyncCache cache, CacheContext c Int key = context.absentKey(); var ready = new AtomicBoolean(); var done = new AtomicBoolean(); + @SuppressWarnings("NullAway") var valueFuture = cache.get(key, k -> { await().untilTrue(ready); return null; @@ -201,6 +208,7 @@ public void getFunc_absent_failure_async(AsyncCache cache, CacheContex @CacheSpec(executor = CacheExecutor.THREADED, executorFailure = ExecutorFailure.IGNORED) public void getFunc_absent_cancelled(AsyncCache cache, CacheContext context) { var done = new AtomicBoolean(); + @SuppressWarnings("NullAway") var valueFuture = cache.get(context.absentKey(), k -> { await().until(done::get); return null; @@ -241,6 +249,7 @@ public void getFunc_present(AsyncCache cache, CacheContext context) { /* --------------- getBiFunc --------------- */ @CacheSpec + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") public void getBiFunc_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> @@ -248,6 +257,7 @@ public void getBiFunc_nullKey(AsyncCache cache, CacheContext context) } @CacheSpec + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") public void getBiFunc_nullLoader(AsyncCache cache, CacheContext context) { BiFunction> mappingFunction = null; @@ -255,6 +265,7 @@ public void getBiFunc_nullLoader(AsyncCache cache, CacheContext contex } @CacheSpec + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") public void getBiFunc_nullKeyAndLoader(AsyncCache cache, CacheContext context) { BiFunction> mappingFunction = null; @@ -372,6 +383,7 @@ public void getBiFunc_present(AsyncCache cache, CacheContext context) /* --------------- getAllFunc --------------- */ @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getAllFunction_nullKeys(AsyncCache cache, CacheContext context) { @@ -380,6 +392,7 @@ public void getAllFunction_nullKeys(AsyncCache cache, CacheContext con } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getAllFunction_nullKeys_nullFunction( @@ -389,6 +402,7 @@ public void getAllFunction_nullKeys_nullFunction( } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getAllFunction_nullFunction(AsyncCache cache, CacheContext context) { @@ -413,6 +427,7 @@ public void getAllFunction_iterable_empty(AsyncCache cache, CacheConte assertThat(result).isExhaustivelyEmpty(); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getAllFunction_nullLookup(AsyncCache cache, CacheContext context) { @@ -443,6 +458,7 @@ public void getAllFunction_immutable_result(AsyncCache cache, CacheCon } @CacheSpec + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") public void getAllFunction_absent_null(AsyncCache cache, CacheContext context) { assertThat(cache.getAll(context.absentKeys(), keys -> null)) @@ -609,6 +625,7 @@ public void getAllFunction_canceled_individual(AsyncCache cache, Cache }); for (var key : context.absentKeys()) { var future = cache.getIfPresent(key); + requireNonNull(future); future.cancel(true); assertThat(future).hasCompletedExceptionally(); } @@ -655,6 +672,7 @@ public void getAllFunction_badLoader(AsyncCache cache, CacheContext co /* --------------- getAllBiFunc --------------- */ @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getAllBifunction_nullKeys(AsyncCache cache, CacheContext context) { @@ -663,6 +681,7 @@ public void getAllBifunction_nullKeys(AsyncCache cache, CacheContext c } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getAllBifunction_nullKeys_nullBifunction( @@ -672,6 +691,7 @@ public void getAllBifunction_nullKeys_nullBifunction( } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getAllBifunction_nullBifunction(AsyncCache cache, CacheContext context) { @@ -938,6 +958,7 @@ public void getAllBifunction_canceled_individual( }); for (var key : context.absentKeys()) { var future = cache.getIfPresent(key); + requireNonNull(future); future.cancel(true); assertThat(future).hasCompletedExceptionally(); } @@ -1010,6 +1031,7 @@ public void getAllBifunction_early_success(AsyncCache cache, CacheCont var result = cache.getAll(context.absentKeys(), (keysToLoad, executor) -> bulk); var future = cache.asMap().get(key); + requireNonNull(future); future.complete(value); bulk.complete(context.absent()); // obtrudes the future's value @@ -1027,6 +1049,8 @@ public void getAllBifunction_early_failure(AsyncCache cache, CacheCont var bulk = new CompletableFuture>(); var result = cache.getAll(context.absentKeys(), (keysToLoad, executor) -> bulk); var future = cache.asMap().get(key); + + requireNonNull(future); future.completeExceptionally(error); bulk.complete(context.absent()); @@ -1043,6 +1067,7 @@ public void getAllBifunction_early_failure(AsyncCache cache, CacheCont /* --------------- put --------------- */ + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void put_nullKey(AsyncCache cache, CacheContext context) { @@ -1050,12 +1075,14 @@ public void put_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> cache.put(null, value)); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void put_nullValue(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> cache.put(context.absentKey(), null)); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void put_nullKeyAndValue(AsyncCache cache, CacheContext context) { 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 index 42d54382ab..a03be45ef8 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java @@ -998,8 +998,13 @@ public void evict_retired_victim(BoundedLocalCache cache, CacheContext @Test(dataProvider = "caches") @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.VALUE) + maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) public void evict_zeroWeight_candidate(BoundedLocalCache cache, CacheContext context) { + when(context.weigher().weigh(any(), any())).thenAnswer(invocation -> { + Int value = invocation.getArgument(1); + return Math.abs(value.intValue()); + }); + for (int i = 0; i < context.maximumSize(); i++) { assertThat(cache.put(Int.valueOf(i), Int.valueOf(1))).isNull(); } @@ -1019,8 +1024,13 @@ public void evict_zeroWeight_candidate(BoundedLocalCache cache, CacheC @Test(dataProvider = "caches") @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.VALUE) + maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) public void evict_zeroWeight_victim(BoundedLocalCache cache, CacheContext context) { + when(context.weigher().weigh(any(), any())).thenAnswer(invocation -> { + Int value = invocation.getArgument(1); + return Math.abs(value.intValue()); + }); + for (int i = 0; i < context.maximumSize(); i++) { assertThat(cache.put(Int.valueOf(i), Int.valueOf(1))).isNull(); } 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 index aa2b2fc4dd..4b01a97910 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java @@ -354,21 +354,25 @@ public void testEqualsAndHashCode() { .testEquals(); } + @SuppressWarnings("NullAway") public void testMaximumWeight_withWeigher() { Caffeine builder = Caffeine.from(parse("maximumWeight=9000")); assertThat(builder.weigher((k, v) -> 42).build(k -> null)).isNotNull(); } + @SuppressWarnings("NullAway") public void testMaximumWeight_withoutWeigher() { Caffeine builder = Caffeine.from(parse("maximumWeight=9000")); assertThrows(IllegalStateException.class, () -> builder.build(k -> null)); } + @SuppressWarnings("NullAway") public void testMaximumSize_withWeigher() { Caffeine builder = Caffeine.from(parse("maximumSize=9000")); assertThat(builder.weigher((k, v) -> 42).build(k -> null)).isNotNull(); } + @SuppressWarnings("NullAway") public void testMaximumSize_withoutWeigher() { Caffeine builder = Caffeine.from(parse("maximumSize=9000")); assertThat(builder.build(k -> null)).isNotNull(); 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 index 7bd8d5d09f..fcc312a5db 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java @@ -28,13 +28,11 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.Mockito; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.github.benmanes.caffeine.cache.stats.CacheStats; import com.github.benmanes.caffeine.cache.stats.StatsCounter; import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; @@ -56,16 +54,11 @@ * @author ben.manes@gmail.com (Ben Manes) */ public final class CaffeineTest { - @Mock StatsCounter statsCounter; - @Mock Expiry expiry; - @Mock CacheLoader loader; + private static final CacheLoader loader = new FatalCacheLoader(); + private static final StatsCounter statsCounter = new FatalStatsCounter(); + private static final Expiry expiry = new FatalExpiry(); - @BeforeClass - public void beforeClass() throws Exception { - MockitoAnnotations.openMocks(this).close(); - } - - @BeforeMethod @AfterMethod + @AfterMethod public void reset() { TestLoggerFactory.clear(); } @@ -104,6 +97,7 @@ public void configured() { } @Test + @SuppressWarnings("NullAway") public void fromSpec_null() { assertThrows(NullPointerException.class, () -> Caffeine.from((CaffeineSpec) null)); } @@ -126,6 +120,7 @@ public void fromSpec() { } @Test + @SuppressWarnings("NullAway") public void fromString_null() { assertThrows(NullPointerException.class, () -> Caffeine.from((String) null)); } @@ -196,6 +191,7 @@ public void calculateHashMapCapacity() { /* --------------- loading --------------- */ @Test + @SuppressWarnings("NullAway") public void loading_nullLoader() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().build(null)); } @@ -224,6 +220,7 @@ public void async_weakKeys_evictionListener() { /* --------------- async loader --------------- */ @Test + @SuppressWarnings("NullAway") public void asyncLoader_nullLoader() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().buildAsync((CacheLoader) null)); @@ -377,6 +374,7 @@ public void maximumWeight_large() { /* --------------- weigher --------------- */ @Test + @SuppressWarnings("NullAway") public void weigher_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().weigher(null)); } @@ -589,6 +587,7 @@ public void expireAfterWrite_duration_excessive() { /* --------------- expiry --------------- */ @Test + @SuppressWarnings("NullAway") public void expireAfter_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().expireAfter(null)); } @@ -744,6 +743,7 @@ public void softValues() { /* --------------- scheduler --------------- */ @Test + @SuppressWarnings("NullAway") public void scheduler_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().scheduler(null)); } @@ -773,6 +773,7 @@ public void scheduler_custom() { /* --------------- executor --------------- */ @Test + @SuppressWarnings("NullAway") public void executor_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().executor(null)); } @@ -793,6 +794,7 @@ public void executor() { /* --------------- ticker --------------- */ @Test + @SuppressWarnings("NullAway") public void ticker_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().ticker(null)); } @@ -815,6 +817,7 @@ public void ticker() { /* --------------- stats --------------- */ @Test + @SuppressWarnings("NullAway") public void recordStats_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().recordStats(null)); } @@ -838,9 +841,19 @@ public void recordStats() { @Test public void recordStats_custom() { - Supplier supplier = () -> statsCounter; - var builder = Caffeine.newBuilder().recordStats(supplier); - builder.statsCounterSupplier.get().recordEviction(1, RemovalCause.SIZE); + StatsCounter statsCounter = Mockito.mock(); + var builder = Caffeine.newBuilder().recordStats(() -> statsCounter); + assertThat(builder.statsCounterSupplier).isNotNull(); + + var counter1 = builder.statsCounterSupplier.get(); + var counter2 = builder.statsCounterSupplier.get(); + assertThat(counter1).isNotSameInstanceAs(counter2); + assertThat(counter1).isNotNull(); + assertThat(counter2).isNotNull(); + + assertThat(counter1.getClass().getName()) + .isEqualTo("com.github.benmanes.caffeine.cache.stats.GuardedStatsCounter"); + counter1.recordEviction(1, RemovalCause.SIZE); verify(statsCounter).recordEviction(1, RemovalCause.SIZE); assertThat(builder.build()).isNotNull(); } @@ -848,6 +861,7 @@ public void recordStats_custom() { /* --------------- removalListener --------------- */ @Test + @SuppressWarnings("NullAway") public void removalListener_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().removalListener(null)); } @@ -869,6 +883,7 @@ public void removalListener() { /* --------------- evictionListener --------------- */ @Test + @SuppressWarnings("NullAway") public void evictionListener_null() { assertThrows(NullPointerException.class, () -> Caffeine.newBuilder().evictionListener(null)); } @@ -886,4 +901,45 @@ public void evictionListener() { assertThat(builder.evictionListener).isSameInstanceAs(removalListener); assertThat(builder.build()).isNotNull(); } + + private static final class FatalCacheLoader implements CacheLoader { + @Override public Object load(Object key) throws Exception { + throw new AssertionError(); + } + } + + private static final class FatalExpiry implements Expiry { + @Override public long expireAfterCreate(Object key, Object value, long currentTime) { + throw new AssertionError(); + } + @Override public long expireAfterUpdate(Object key, Object value, + long currentTime, long currentDuration) { + throw new AssertionError(); + } + @Override public long expireAfterRead(Object key, Object value, + long currentTime, long currentDuration) { + throw new AssertionError(); + } + } + + private static final class FatalStatsCounter implements StatsCounter { + @Override public void recordHits(int count) { + throw new AssertionError(); + } + @Override public void recordMisses(int count) { + throw new AssertionError(); + } + @Override public void recordLoadSuccess(long loadTime) { + throw new AssertionError(); + } + @Override public void recordLoadFailure(long loadTime) { + throw new AssertionError(); + } + @Override public void recordEviction(int weight, RemovalCause cause) { + throw new AssertionError(); + } + @Override public CacheStats snapshot() { + throw new AssertionError(); + } + } } 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 index ca451325d4..e72fe319fe 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java @@ -1199,12 +1199,14 @@ public void coldest_snapshot(Cache cache, assertThat(coldest).containsExactlyEntriesIn(context.original()); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void coldestFunc_null(CacheContext context, Eviction eviction) { assertThrows(NullPointerException.class, () -> eviction.coldest(null)); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void coldestFunc_nullResult(CacheContext context, Eviction eviction) { @@ -1404,12 +1406,14 @@ public void hottest_snapshot(Cache cache, assertThat(hottest).containsExactlyEntriesIn(context.original()); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void hottestFunc_null(CacheContext context, Eviction eviction) { assertThrows(NullPointerException.class, () -> eviction.hottest(null)); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void hottestFunc_nullResult(CacheContext context, Eviction eviction) { 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 d447b056c6..8c2579cf71 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 @@ -34,6 +34,7 @@ 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.Objects.requireNonNull; import static java.util.function.Function.identity; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -57,6 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; +import org.jspecify.annotations.Nullable; import org.mockito.Mockito; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @@ -961,6 +963,7 @@ public void setExpiresAfter_expired(Cache cache, /* --------------- Policy: putIfAbsent --------------- */ @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -971,6 +974,7 @@ public void putIfAbsent_nullKey(Cache cache, } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -981,6 +985,7 @@ public void putIfAbsent_nullValue(Cache cache, } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -1001,6 +1006,7 @@ public void putIfAbsent_negativeDuration(Cache cache, } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -1062,6 +1068,7 @@ public void putIfAbsent_present(Cache cache, /* --------------- Policy: put --------------- */ @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -1072,6 +1079,7 @@ public void put_nullKey(Cache cache, } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -1082,6 +1090,7 @@ public void put_nullValue(Cache cache, } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -1115,6 +1124,7 @@ public void put_excessiveDuration(Cache cache, } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) @@ -1162,6 +1172,7 @@ public void put_replace(Cache cache, /* --------------- Policy: compute --------------- */ + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expiry = CacheExpiry.ACCESS, removalListener = {Listener.DISABLED, Listener.REJECTING}) public void compute_nullKey(CacheContext context, VarExpiration expireAfterVar) { @@ -1170,6 +1181,7 @@ public void compute_nullKey(CacheContext context, VarExpiration expire } @CheckNoStats + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expiry = CacheExpiry.ACCESS, removalListener = {Listener.DISABLED, Listener.REJECTING}) public void compute_nullMappingFunction(CacheContext context, @@ -1227,8 +1239,8 @@ public void compute_remove(Cache cache, @SuppressWarnings("CheckReturnValue") @CacheSpec(expiry = CacheExpiry.ACCESS) public void compute_recursive(CacheContext context, VarExpiration expireAfterVar) { - var mappingFunction = new BiFunction() { - @Override public Int apply(Int key, Int value) { + var mappingFunction = new BiFunction() { + @Override public @Nullable Int apply(Int key, Int value) { return expireAfterVar.compute(key, this, Duration.ofDays(1)); } }; @@ -1243,8 +1255,8 @@ public void compute_recursive(CacheContext context, VarExpiration expi 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) { + var mappingFunction = new BiFunction() { + @Override public @Nullable Int apply(Int key, Int value) { return expireAfterVar.compute(key.equals(key1) ? key2 : key1, this, Duration.ofDays(1)); } }; @@ -1416,7 +1428,7 @@ public void compute_differentValue(Cache cache, var replaced = new HashMap(); var duration = context.expiryTime().duration().dividedBy(2); for (Int key : context.firstMiddleLastKeys()) { - Int value = context.original().get(key); + Int value = requireNonNull(context.original().get(key)); Int result = expireAfterVar.compute(key, (k, v) -> value.negate(), duration); replaced.put(key, value); @@ -1679,12 +1691,14 @@ public void oldest_snapshot(Cache cache, assertThat(oldest).containsExactlyEntriesIn(context.original()); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expiry = CacheExpiry.ACCESS) public void oldestFunc_null(CacheContext context, VarExpiration expireAfterVar) { assertThrows(NullPointerException.class, () -> expireAfterVar.oldest(null)); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expiry = CacheExpiry.ACCESS) public void oldestFunc_nullResult(CacheContext context, VarExpiration expireAfterVar) { @@ -1819,12 +1833,14 @@ public void youngest_snapshot(Cache cache, assertThat(youngest).containsExactlyEntriesIn(context.original()); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expiry = CacheExpiry.ACCESS) public void youngestFunc_null(CacheContext context, VarExpiration expireAfterVar) { assertThrows(NullPointerException.class, () -> expireAfterVar.youngest(null)); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expiry = CacheExpiry.ACCESS) public void youngestFunc_nullResult(CacheContext context, 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 index 2f3ee7914f..d7313dbe15 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java @@ -27,6 +27,7 @@ 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.Objects.requireNonNull; import static org.junit.Assert.assertThrows; import static org.slf4j.event.Level.WARN; @@ -94,7 +95,7 @@ public void getIfPresent(Cache cache, CacheContext context) { 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; + Function mappingFunction = key -> requireNonNull(context.original().get(key)); context.ticker().advance(Duration.ofSeconds(30)); var value = cache.get(context.firstKey(), mappingFunction); assertThat(value).isEqualTo(context.original().get(context.firstKey())); @@ -395,6 +396,7 @@ public void oldest_snapshot(Cache cache, CacheContext context, assertThat(oldest).containsExactlyEntriesIn(context.original()); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) public void oldestFunc_null(CacheContext context, @@ -406,6 +408,7 @@ public void oldestFunc_null(CacheContext context, @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) public void oldestFunc_nullResult(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { + @SuppressWarnings("NullAway") var result = expireAfterWrite.oldest(stream -> null); assertThat(result).isNull(); } @@ -544,6 +547,7 @@ public void youngest_snapshot(Cache cache, CacheContext context, assertThat(youngest).containsExactlyEntriesIn(context.original()); } + @SuppressWarnings("NullAway") @Test(dataProvider = "caches") @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) public void youngestFunc_null(CacheContext context, @@ -555,6 +559,7 @@ public void youngestFunc_null(CacheContext context, @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) public void youngestFunc_nullResult(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { + @SuppressWarnings("NullAway") var result = expireAfterWrite.youngest(stream -> null); assertThat(result).isNull(); } 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 index 662caaa84c..c72e9fad5f 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java @@ -65,6 +65,7 @@ public static TestSuite suite() { .createTestSuite(); } + @SuppressWarnings("NullAway") @Test(dataProvider = "interners") public void intern_null(Interner interner) { assertThrows(NullPointerException.class, () -> interner.intern(null)); @@ -143,6 +144,7 @@ public void nullPointerExceptions() { } @Test + @SuppressWarnings("NullAway") public void factory() { assertThat(Interned.FACTORY.newReferenceKey(new Object(), null)) .isInstanceOf(WeakKeyEqualsReference.class); 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 index 209b281e48..f1766c5e76 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java @@ -16,9 +16,12 @@ package com.github.benmanes.caffeine.cache; import static com.google.common.truth.Truth.assertAbout; +import static java.util.Objects.requireNonNull; import java.util.Iterator; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.testing.CollectionSubject; import com.google.common.collect.Sets; import com.google.common.truth.FailureMetadata; @@ -32,9 +35,10 @@ final class LinkedDequeSubject extends CollectionSubject { private final LinkedDeque actual; @SuppressWarnings("unchecked") - private LinkedDequeSubject(FailureMetadata metadata, LinkedDeque subject) { + private LinkedDequeSubject(FailureMetadata metadata, @Nullable LinkedDeque subject) { super(metadata, subject); - this.actual = (LinkedDeque) subject; + this.actual = requireNonNull((LinkedDeque) subject); + } public static Factory> deque() { 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 index e4163ad913..13890f5654 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java @@ -19,6 +19,7 @@ import static com.github.benmanes.caffeine.testing.Awaits.await; import static com.github.benmanes.caffeine.testing.MapSubject.map; import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.requireNonNull; import java.util.Collection; import java.util.Map; @@ -26,6 +27,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.cache.Async.AsyncWeigher; import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalAsyncCache; import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalAsyncLoadingCache; @@ -38,7 +41,7 @@ import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalAsyncLoadingCache; import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalManualCache; import com.github.benmanes.caffeine.cache.stats.StatsCounter; -import com.github.benmanes.caffeine.cache.testing.Weighers; +import com.github.benmanes.caffeine.cache.testing.Weighers.SkippedWeigher; import com.google.common.collect.ImmutableTable; import com.google.common.collect.Sets; import com.google.common.truth.FailureMetadata; @@ -54,9 +57,9 @@ public final class LocalCacheSubject extends Subject { private final Object actual; - private LocalCacheSubject(FailureMetadata metadata, Object subject) { + private LocalCacheSubject(FailureMetadata metadata, @Nullable Object subject) { super(metadata, subject); - this.actual = subject; + this.actual = requireNonNull(subject); } public static Factory> asyncLocal() { @@ -298,11 +301,12 @@ private void checkLinks(BoundedLocalCache bounded, long totalWeightedSize = 0; Set> seen = Sets.newIdentityHashSet(); for (var cell : deques.cellSet()) { - long weightedSize = scanLinks(bounded, cell.getValue(), seen); - check("%s: %s in %s", cell.getRowKey(), cell.getValue(), bounded.data) + var deque = requireNonNull(cell.getValue()); + long weightedSize = scanLinks(bounded, deque, seen); + check("%s: %s in %s", cell.getRowKey(), deque, bounded.data) .that(weightedSize).isEqualTo(cell.getColumnKey()); - totalSize += cell.getValue().size(); totalWeightedSize += weightedSize; + totalSize += deque.size(); } check("linkSize").withMessage("cache.size() != links").that(bounded).hasSize(seen.size()); check("totalSize").withMessage("cache.size() == deque.size()").that(bounded).hasSize(totalSize); @@ -354,7 +358,7 @@ private void checkNode(BoundedLocalCache bounded, Node bounded, - Node node, Object key, Object value) { + Node node, @Nullable Object key, @Nullable Object value) { if (bounded.collectKeys()) { if ((key != null) && (value != null)) { check("bounded").that(bounded).containsKey(key); @@ -368,10 +372,11 @@ private void checkKey(BoundedLocalCache bounded, } private void checkValue(BoundedLocalCache bounded, - Node node, Object key, Object value) { + Node node, @Nullable Object key, @Nullable Object value) { if (!bounded.collectValues()) { check("value").that(value).isNotNull(); if ((key != null) && !bounded.hasExpired(node, bounded.expirationTicker().read())) { + requireNonNull(value); check("containsValue(value) for key %s", key) .about(map()).that(bounded).containsValue(value); } @@ -379,7 +384,7 @@ private void checkValue(BoundedLocalCache bounded, checkIfAsyncValue(value); } - private void checkIfAsyncValue(Object value) { + private void checkIfAsyncValue(@Nullable Object value) { if (value instanceof CompletableFuture) { var future = (CompletableFuture) value; if (!future.isDone() || future.isCompletedExceptionally()) { @@ -390,18 +395,22 @@ private void checkIfAsyncValue(Object value) { } private void checkWeight(BoundedLocalCache bounded, - Node node, Object key, Object value) { + Node node, @Nullable Object key, @Nullable Object value) { check("node.getWeight").that(node.getWeight()).isAtLeast(0); + if ((key == null) || (value == null)) { + return; + } - var weigher = bounded.weigher; - boolean canCheckWeight = (weigher == Weighers.random()); + Weigher weigher = bounded.weigher; if (weigher instanceof AsyncWeigher) { - @SuppressWarnings("rawtypes") - var asyncWeigher = (AsyncWeigher) weigher; - canCheckWeight = (asyncWeigher.delegate == Weighers.random()); + weigher = ((AsyncWeigher) weigher).delegate; + } + if (weigher instanceof BoundedWeigher) { + weigher = ((BoundedWeigher) weigher).delegate; } - if (canCheckWeight) { - check("node.getWeight()").that(node.getWeight()).isEqualTo(weigher.weigh(key, value)); + if (!(weigher instanceof SkippedWeigher)) { + int weight = bounded.weigher.weigh(key, value); + check("node.getWeight()").that(node.getWeight()).isEqualTo(weight); } } 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 index 550f1e0f40..2f0c1f7d06 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.STRICT_STUBS; import java.util.Random; import java.util.concurrent.CompletableFuture; @@ -28,8 +29,10 @@ import java.util.concurrent.TimeUnit; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.testng.MockitoSettings; +import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.google.common.primitives.Ints; @@ -38,6 +41,8 @@ * @author ben.manes@gmail.com (Ben Manes) */ @Test(singleThreaded = true) +@Listeners(MockitoTestNGListener.class) +@MockitoSettings(strictness = STRICT_STUBS) public final class PacerTest { private static final long ONE_MINUTE_IN_NANOS = TimeUnit.MINUTES.toNanos(1); private static final Random random = new Random(); @@ -51,8 +56,7 @@ public final class PacerTest { Pacer pacer; @BeforeMethod - public void beforeMethod() throws Exception { - MockitoAnnotations.openMocks(this).close(); + public void beforeMethod() { pacer = new Pacer(scheduler); } 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 253b11f3c6..cef2647fe9 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 @@ -30,6 +30,7 @@ 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.Objects.requireNonNull; import static java.util.function.Function.identity; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -299,7 +300,8 @@ public void invalidateAll_iterable(Cache cache, CacheContext context) List> collected; var keys = context.firstMiddleLastKeys(); if (context.isStrongValues()) { - retained = Maps.toMap(context.firstMiddleLastKeys(), key -> context.original().get(key)); + retained = Maps.toMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key))); collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(keys::contains))); } else { @@ -335,7 +337,8 @@ public void invalidateAll_full(Cache cache, CacheContext context) { List> collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(keys::contains))); if (context.isStrongValues()) { - retained = Maps.toMap(context.firstMiddleLastKeys(), key -> context.original().get(key)); + retained = Maps.toMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key))); } else { retained = Map.of(); for (var key : keys) { @@ -589,7 +592,8 @@ public void containsValue(Map map, CacheContext context) { maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, stats = Stats.ENABLED, removalListener = Listener.CONSUMING) public void clear(Map map, CacheContext context) { - var retained = Maps.toMap(context.firstMiddleLastKeys(), key -> context.original().get(key)); + var retained = Maps.toMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key))); var collected = getExpectedAfterGc(context, Maps.difference( context.original(), retained).entriesOnlyOnLeft()); 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 2be25f8e83..efb7a137d9 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 @@ -30,6 +30,7 @@ import static com.github.benmanes.caffeine.testing.LoggingEvents.logEvents; import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThrows; @@ -230,7 +231,7 @@ public void refreshIfNeeded_replaced(LoadingCache cache, CacheContext assertThat(value).isNotNull(); assertThat(cache.policy().refreshes()).isNotEmpty(); - var future = cache.policy().refreshes().get(context.firstKey()); + var future = requireNonNull(cache.policy().refreshes().get(context.firstKey())); cache.put(context.firstKey(), context.absentValue()); future.complete(context.absentKey().negate()); @@ -256,7 +257,7 @@ public void refreshIfNeeded_expired(LoadingCache cache, CacheContext c assertThat(value).isNotNull(); assertThat(cache.policy().refreshes()).isNotEmpty(); - var future = cache.policy().refreshes().get(context.firstKey()); + var future = requireNonNull(cache.policy().refreshes().get(context.firstKey())); context.ticker().advance(Duration.ofMinutes(1)); future.complete(context.absentValue()); @@ -280,7 +281,7 @@ public void refreshIfNeeded_absent_newValue(LoadingCache cache, CacheC assertThat(value).isNotNull(); assertThat(cache.policy().refreshes()).isNotEmpty(); - var future = cache.policy().refreshes().get(context.firstKey()); + var future = requireNonNull(cache.policy().refreshes().get(context.firstKey())); cache.invalidate(context.firstKey()); assertThat(cache).doesNotContainKey(context.firstKey()); @@ -306,7 +307,7 @@ public void refreshIfNeeded_absent_nullValue(LoadingCache cache, Cache assertThat(value).isNotNull(); assertThat(cache.policy().refreshes()).isNotEmpty(); - var future = cache.policy().refreshes().get(context.firstKey()); + var future = requireNonNull(cache.policy().refreshes().get(context.firstKey())); cache.invalidate(context.firstKey()); future.complete(null); @@ -404,6 +405,7 @@ public void refreshIfNeeded_nullFuture(CacheContext context) { @Override public Int load(Int key) { throw new IllegalStateException(); } + @SuppressWarnings("NullAway") @Override public CompletableFuture asyncReload( Int key, Int oldValue, Executor executor) { refreshed.set(true); @@ -521,7 +523,8 @@ public void getAllPresent_delayed(LoadingCache cache, CacheContext con if (context.isCaffeine()) { var replaced = new HashMap(); for (var key : context.firstMiddleLastKeys()) { - cache.policy().refreshes().get(key).complete(key); + var future = requireNonNull(cache.policy().refreshes().get(key)); + future.complete(key); replaced.put(key, context.original().get(key)); } assertThat(context).removalNotifications().withCause(REPLACED) @@ -554,7 +557,7 @@ public void getFunc_immediate(LoadingCache cache, CacheContext context @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; + Function mappingFunction = key -> requireNonNull(context.original().get(key)); context.ticker().advance(Duration.ofSeconds(30)); var value = cache.get(context.firstKey(), mappingFunction); assertThat(value).isNotNull(); @@ -575,7 +578,7 @@ public void getFunc_delayed(LoadingCache cache, CacheContext context) @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL }) public void getFunc_async(AsyncLoadingCache cache, CacheContext context) { - Function mappingFunction = context.original()::get; + Function mappingFunction = key -> requireNonNull(context.original().get(key)); context.ticker().advance(Duration.ofSeconds(30)); cache.get(context.firstKey(), mappingFunction).join(); context.ticker().advance(Duration.ofSeconds(45)); @@ -620,7 +623,8 @@ public void get_delayed(LoadingCache cache, CacheContext context) { assertThat(cache.get(context.firstKey())).isEqualTo(context.firstKey().negate()); if (context.isCaffeine()) { - cache.policy().refreshes().get(context.firstKey()).complete(context.firstKey()); + var future = requireNonNull(cache.policy().refreshes().get(context.firstKey())); + future.complete(context.firstKey()); assertThat(context).removalNotifications().withCause(REPLACED) .contains(context.firstKey(), context.original().get(context.firstKey())) .exclusively(); @@ -751,7 +755,8 @@ public void getAll_delayed(LoadingCache cache, CacheContext context) { if (context.isCaffeine()) { for (var key : keys) { - cache.policy().refreshes().get(key).complete(key); + var future = requireNonNull(cache.policy().refreshes().get(key)); + future.complete(key); } assertThat(context).removalNotifications().withCause(REPLACED) .contains(Maps.filterKeys(context.original(), context.firstMiddleLastKeys()::contains)) @@ -915,6 +920,7 @@ public void refreshes(LoadingCache cache, CacheContext context) { var future = cache.policy().refreshes().get(context.firstKey()); assertThat(future).isNotNull(); + requireNonNull(future); future.complete(Int.MAX_VALUE); assertThat(cache.policy().refreshes()).isExhaustivelyEmpty(); @@ -929,7 +935,7 @@ public void refreshes_nullLookup(LoadingCache cache, CacheContext cont var value = cache.getIfPresent(context.firstKey()); assertThat(value).isNotNull(); - var future = cache.policy().refreshes().get(context.firstKey()); + var future = requireNonNull(cache.policy().refreshes().get(context.firstKey())); assertThat(cache.policy().refreshes().get(null)).isNull(); assertThat(cache.policy().refreshes().containsKey(null)).isFalse(); assertThat(cache.policy().refreshes().containsValue(null)).isFalse(); 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 index 533d351ef9..46de128f79 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java @@ -17,6 +17,9 @@ import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.asyncCache; import static com.github.benmanes.caffeine.cache.testing.CacheSubject.cache; +import static java.util.Objects.requireNonNull; + +import org.jspecify.annotations.Nullable; import com.github.benmanes.caffeine.cache.Async.AsyncEvictionListener; import com.github.benmanes.caffeine.cache.Async.AsyncExpiry; @@ -39,9 +42,9 @@ public final class ReserializableSubject extends Subject { private final Object actual; - private ReserializableSubject(FailureMetadata metadata, Object subject) { + private ReserializableSubject(FailureMetadata metadata, @Nullable Object subject) { super(metadata, subject); - this.actual = subject; + this.actual = requireNonNull(subject); } public static Factory> asyncReserializable() { @@ -257,7 +260,8 @@ private void checkUnboundedLocalCache( UnboundedLocalCache original, UnboundedLocalCache copy) { check("isRecordingStats").that(copy.isRecordingStats).isEqualTo(original.isRecordingStats); - if (original.removalListener == null) { + if ((original.removalListener == null) || (copy.removalListener == null)) { + check("removalListener").that(original.removalListener).isNull(); check("removalListener").that(copy.removalListener).isNull(); } else if (copy.removalListener.getClass() != original.removalListener.getClass()) { check("removalListener").that(copy.removalListener).isNotNull(); 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 index 5864fd7cc3..fd5b09fe90 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java @@ -125,6 +125,7 @@ public void disabledFuture_null() { /* --------------- guarded --------------- */ @Test + @SuppressWarnings("NullAway") public void guardedScheduler_null() { assertThrows(NullPointerException.class, () -> Scheduler.guardedScheduler(null)); } @@ -166,6 +167,7 @@ public void guardedScheduler_exception() { /* --------------- ScheduledExecutorService --------------- */ @Test + @SuppressWarnings("NullAway") public void scheduledExecutorService_null() { assertThrows(NullPointerException.class, () -> Scheduler.forScheduledExecutorService(null)); } 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 index f617d5d2ca..cb98552170 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java @@ -138,7 +138,7 @@ private void status() { System.out.printf(US, "Size = %,d (max: %,d)%n", local.data.mappingCount(), workload.maxEntries); System.out.printf(US, "Lock = [%s%n", StringUtils.substringAfter(evictionLock.toString(), "[")); - System.out.printf(US, "Pending reloads = %,d%n", local.refreshes.size()); + System.out.printf(US, "Pending reloads = %,d%n", local.refreshes().size()); System.out.printf(US, "Pending tasks = %,d%n", ForkJoinPool.commonPool().getQueuedSubmissionCount()); 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 index 4141be2b87..ec2f23b695 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.LENIENT; import java.lang.ref.ReferenceQueue; import java.time.Duration; @@ -48,11 +49,13 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.testng.MockitoSettings; +import org.mockito.testng.MockitoTestNGListener; import org.testng.ITestResult; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.github.benmanes.caffeine.cache.TimerWheel.Sentinel; @@ -65,6 +68,8 @@ * @author ben.manes@gmail.com (Ben Manes) */ @Test(singleThreaded = true) +@MockitoSettings(strictness = LENIENT) +@Listeners(MockitoTestNGListener.class) @SuppressWarnings({"ClassEscapesDefinedScope", "GuardedBy"}) public final class TimerWheelTest { private static final Random random = new Random(); @@ -76,9 +81,8 @@ public final class TimerWheelTest { TimerWheel timerWheel; @BeforeMethod - public void beforeMethod() throws Exception { + public void beforeMethod() { Reset.setThreadLocalRandom(random.nextInt(), random.nextInt()); - MockitoAnnotations.openMocks(this).close(); timerWheel = new TimerWheel<>(); } @@ -203,17 +207,13 @@ public void advance_exception() { @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]); @@ -221,9 +221,7 @@ public void getExpirationDelay_firstWheel(long clock) { @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); 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 index 02d56c2bb0..d2adb0dd9b 100644 --- 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 @@ -20,10 +20,13 @@ 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; +import static java.util.Objects.requireNonNull; import java.util.Map; import java.util.concurrent.Future; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.cache.AsyncCache; import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; @@ -36,9 +39,9 @@ public final class AsyncCacheSubject extends Subject { private final AsyncCache actual; - private AsyncCacheSubject(FailureMetadata metadata, AsyncCache subject) { + private AsyncCacheSubject(FailureMetadata metadata, @Nullable AsyncCache subject) { super(metadata, subject); - this.actual = subject; + this.actual = requireNonNull(subject); } public static Factory> asyncCache() { @@ -81,7 +84,7 @@ public void doesNotContainKey(Object key) { } /** Fails if the cache does not contain the given value. */ - public void containsValue(Object value) { + public void containsValue(@Nullable Object value) { if (value instanceof Future) { check("cache").about(map()).that(actual.asMap()).containsValue(value); } else { @@ -90,7 +93,7 @@ public void containsValue(Object value) { } /** Fails if the cache does not contain the given entry. */ - public void containsEntry(Object key, Object value) { + public void containsEntry(Object key, @Nullable Object value) { if (value instanceof Future) { check("cache").that(actual.asMap()).containsEntry(key, value); } else { 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 index 016c317c0b..26a0d8d8eb 100644 --- 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 @@ -30,6 +30,7 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.function.Function; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import com.github.benmanes.caffeine.cache.AsyncCache; @@ -92,7 +93,6 @@ public final class CacheContext { final TrackingExecutor executor; final ReferenceType keyStrength; final CacheWeigher cacheWeigher; - final Expiry expiry; final Map original; final CacheExpiry expiryType; final Population population; @@ -106,6 +106,8 @@ public final class CacheContext { final Loader loader; final Stats stats; + final @Nullable Expiry expiry; + final boolean isAsyncLoader; CacheBuilder guava; @@ -124,7 +126,7 @@ public final class CacheContext { @Nullable Map absent; - @SuppressWarnings({"PMD.ExcessiveParameterList", "TooManyParameters"}) + @SuppressWarnings({"NullAway.Init", "PMD.ExcessiveParameterList", "TooManyParameters"}) public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher cacheWeigher, Maximum maximumSize, CacheExpiry expiryType, Expire afterAccess, Expire afterWrite, Expire refresh, ReferenceType keyStrength, ReferenceType valueStrength, @@ -159,7 +161,7 @@ public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher c this.compute = compute; this.expiryType = expiryType; this.expiryTime = cacheSpec.expiryTime(); - this.expiry = expiryType.createExpiry(expiryTime); + this.expiry = (expiryType == CacheExpiry.DISABLED) ? null : expiryType.createExpiry(expiryTime); } /** Returns a thread local interner for explicit caching. */ @@ -214,16 +216,19 @@ public Population population() { public Int firstKey() { assertWithMessage("Invalid usage of context").that(firstKey).isNotNull(); + requireNonNull(firstKey); return firstKey; } public Int middleKey() { assertWithMessage("Invalid usage of context").that(middleKey).isNotNull(); + requireNonNull(middleKey); return middleKey; } public Int lastKey() { assertWithMessage("Invalid usage of context").that(lastKey).isNotNull(); + requireNonNull(lastKey); return lastKey; } @@ -417,6 +422,7 @@ public boolean expiresVariably() { return (expiryType != CacheExpiry.DISABLED); } + @NullUnmarked public Expiry expiry() { return expiry; } 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 index a61b262db9..099e1646f3 100644 --- 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 @@ -39,6 +39,8 @@ import java.util.function.Function; import java.util.function.ToLongFunction; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Policy.CacheEntry; import com.github.benmanes.caffeine.cache.RemovalCause; @@ -65,9 +67,9 @@ public final class CacheContextSubject extends Subject { private final CacheContext actual; - CacheContextSubject(FailureMetadata metadata, CacheContext subject) { + CacheContextSubject(FailureMetadata metadata, @Nullable CacheContext subject) { super(metadata, subject); - this.actual = subject; + this.actual = requireNonNull(subject); } public static Factory context() { @@ -212,9 +214,9 @@ public static final class StatsSubject extends Subject { private final CacheContext actual; private final boolean isDirect; - private StatsSubject(FailureMetadata metadata, CacheContext context) { + private StatsSubject(FailureMetadata metadata, @Nullable CacheContext context) { super(metadata, context); - this.actual = context; + this.actual = requireNonNull(context); this.isDirect = !context.isRecordingStats() || (context.executorType() == CacheExecutor.DIRECT); } @@ -290,6 +292,7 @@ private ListenerSubject(FailureMetadata metadata, CacheContext context, private static Factory factoryOf( RemovalListenerType... removalListenerTypes) { return (metadata, context) -> { + requireNonNull(context); var subject = Arrays.stream(removalListenerTypes) .filter(listener -> listener.isConsumingListener(context)) .collect(toImmutableMap(identity(), listener -> listener.instance(context))); @@ -356,7 +359,7 @@ private WithCause(RemovalCause cause) { } @CanIgnoreReturnValue - public Exclusive contains(Int key, Int value) { + public Exclusive contains(Int key, @Nullable Int value) { return contains(new SimpleEntry<>(key, value)); } 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 index 69f6cfa86c..d1d5c7e96d 100644 --- 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 @@ -54,6 +54,7 @@ import com.github.benmanes.caffeine.cache.Scheduler; import com.github.benmanes.caffeine.cache.Weigher; import com.github.benmanes.caffeine.cache.testing.RemovalListeners.ConsumingRemovalListener; +import com.github.benmanes.caffeine.cache.testing.Weighers.SkippedWeigher; import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; import com.github.benmanes.caffeine.testing.Int; import com.google.common.collect.Maps; @@ -198,12 +199,18 @@ enum CacheWeigher { VALUE(() -> (key, value) -> Math.abs(((Int) value).intValue())), /** A flag indicating that the entry is weighted by the value's collection size. */ COLLECTION(() -> (key, value) -> ((Collection) value).size()), - /** A flag indicating that the entry's weight is randomly changing. */ + /** + * A flag indicating that the entry's weight is randomly changing and is skipped by automatic + * validation checks. + */ RANDOM(Weighers::random), - /** A flag indicating that the entry's weight records interactions. */ + /** + * A flag indicating that the entry's weight records interactions and is skipped by automatic + * automatic validation checks. + */ @SuppressWarnings("unchecked") MOCKITO(() -> { - Weigher weigher = Mockito.mock(); + SkippedWeigher weigher = Mockito.mock(); when(weigher.weigh(any(), any())).thenReturn(1); return weigher; }); @@ -267,7 +274,7 @@ CacheExpiry[] expiry() default { enum CacheExpiry { DISABLED { @Override public Expiry createExpiry(Expire expiryTime) { - return null; + throw new AssertionError(); } }, MOCKITO { @@ -419,6 +426,7 @@ enum Loader implements CacheLoader { }, /** A loader that always returns null (no mapping). */ NULL { + @SuppressWarnings("NullAway") @Override public Int load(Int key) { return null; } @@ -461,7 +469,9 @@ enum Loader implements CacheLoader { @Override public Int load(Int key) { throw new UnsupportedOperationException(); } - @SuppressWarnings({"PMD.ReturnEmptyCollectionRatherThanNull", "ReturnsNullCollection"}) + + @SuppressWarnings({"NullAway", + "PMD.ReturnEmptyCollectionRatherThanNull", "ReturnsNullCollection"}) @Override public Map loadAll(Set keys) { return null; } 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 index 726e299940..c37db5ff51 100644 --- 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 @@ -21,10 +21,13 @@ 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; +import static java.util.Objects.requireNonNull; import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.cache.Cache; import com.google.common.testing.GcFinalization; import com.google.common.truth.Correspondence; @@ -44,9 +47,9 @@ public final class CacheSubject extends Subject { private final Cache actual; - CacheSubject(FailureMetadata metadata, Cache subject) { + CacheSubject(FailureMetadata metadata, @Nullable Cache subject) { super(metadata, subject); - this.actual = subject; + this.actual = requireNonNull(subject); } public static Factory> cache() { @@ -104,7 +107,7 @@ public void doesNotContainKey(Object key) { } /** Fails if the cache does not contain the given value. */ - public void containsValue(Object value) { + public void containsValue(@Nullable Object value) { check("cache").about(map()).that(actual.asMap()).containsValue(value); } @@ -114,14 +117,16 @@ public void doesNotContainValue(Object value) { } /** Fails if the cache does not contain the given entry. */ - public void containsEntry(Object key, Object value) { + public void containsEntry(Object key, @Nullable Object value) { + requireNonNull(value); check("cache").that(actual.asMap()) .comparingValuesUsing(EQUALITY) .containsEntry(key, value); } /** Fails if the cache contains the given entry. */ - public void doesNotContainEntry(Object key, Object value) { + public void doesNotContainEntry(Object key, @Nullable Object value) { + requireNonNull(value); check("cache").that(actual.asMap()) .comparingValuesUsing(EQUALITY) .doesNotContainEntry(key, value); @@ -162,8 +167,8 @@ public static final class CleanUpSubject extends Subject { private final Cache actual; - private CleanUpSubject(FailureMetadata metadata, Cache cache) { - super(metadata, cache.asMap()); + private CleanUpSubject(FailureMetadata metadata, @Nullable Cache cache) { + super(metadata, requireNonNull(cache).asMap()); this.actual = cache; } 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 index 712338ba5e..1a6af68282 100644 --- 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 @@ -34,6 +34,8 @@ import java.util.function.BiFunction; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.Policy; @@ -138,7 +140,8 @@ public static Cache newGuavaCache(CacheContext context) { return castedCache; } - static class GuavaCache implements Cache, Serializable { + @SuppressWarnings("NullAway") + static class GuavaCache implements Cache, Serializable { private static final long serialVersionUID = 1L; private final com.google.common.cache.Cache cache; @@ -146,10 +149,10 @@ static class GuavaCache implements Cache, Serializable { private final boolean canSnapshot; private final Ticker ticker; - transient ConcurrentMap mapView; transient StatsCounter statsCounter; - transient Policy policy; - transient Set keySet; + transient @Nullable ConcurrentMap mapView; + transient @Nullable Policy policy; + transient @Nullable Set keySet; GuavaCache(com.google.common.cache.Cache cache, CacheContext context) { this.canSnapshot = context.expires() || context.refreshes(); @@ -160,12 +163,12 @@ static class GuavaCache implements Cache, Serializable { } @Override - public V getIfPresent(Object key) { + public @Nullable V getIfPresent(Object key) { return cache.getIfPresent(key); } @Override - public V get(K key, Function mappingFunction) { + public @Nullable V get(K key, Function mappingFunction) { requireNonNull(mappingFunction); try { return cache.get(key, () -> { @@ -201,7 +204,7 @@ public Map getAll(Iterable keys, Function found = getAllPresent(keys); + Map found = getAllPresent(keys); Set keysToLoad = Sets.difference(ImmutableSet.copyOf(keys), found.keySet()); if (keysToLoad.isEmpty()) { return found; @@ -494,6 +497,7 @@ static class GuavaLoadingCache extends GuavaCache implements Loading } @Override + @SuppressWarnings("NullAway") public V get(K key) { try { return cache.get(key); 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 index 18a517e025..4f98c8b5a2 100644 --- 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 @@ -22,6 +22,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.RejectedExecutionException; +import org.jspecify.annotations.Nullable; + import com.github.benmanes.caffeine.cache.RemovalCause; import com.github.benmanes.caffeine.cache.RemovalListener; @@ -44,7 +46,7 @@ public static RemovalListener rejecting() { return new RejectingRemovalListener<>(); } - private static void validate(Object key, Object value, RemovalCause cause) { + private static void validate(@Nullable Object key, @Nullable Object value, RemovalCause cause) { if (cause != RemovalCause.COLLECTED) { requireNonNull(key); requireNonNull(value); @@ -60,7 +62,7 @@ public static final class RejectingRemovalListener public int rejected; @Override - public void onRemoval(K key, V value, RemovalCause cause) { + public void onRemoval(@Nullable K key, @Nullable V value, RemovalCause cause) { validate(key, value, cause); if (reject) { @@ -83,7 +85,7 @@ public ConsumingRemovalListener() { } @Override - public void onRemoval(K key, V value, RemovalCause cause) { + public void onRemoval(@Nullable K key, @Nullable V value, RemovalCause cause) { validate(key, value, cause); removed.add(new RemovalNotification<>(key, value, cause)); } 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 index d1897c0354..b690593398 100644 --- 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 @@ -42,6 +42,9 @@ public static Weigher random() { return (Weigher) RandomWeigher.INSTANCE; } + /** A marker to instruct the validation to not check the entry's weight for data consistency. */ + public interface SkippedWeigher extends Weigher {} + static final class ConstantWeigher implements Weigher, Serializable { private static final long serialVersionUID = 1L; @@ -57,7 +60,7 @@ static final class ConstantWeigher implements Weigher, Serializable } } - enum RandomWeigher implements Weigher { + enum RandomWeigher implements SkippedWeigher { INSTANCE; @Override public int weigh(Object key, Object value) { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java index e9addaf1a2..780306989a 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/eclipse/acceptance/ConcurrentHashMapAcceptanceTest.java @@ -25,6 +25,7 @@ import org.eclipse.collections.impl.list.Interval; import org.eclipse.collections.impl.parallel.ParallelIterate; import org.eclipse.collections.impl.test.Verify; +import org.jspecify.annotations.NullUnmarked; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -35,6 +36,7 @@ * * Ported from Eclipse Collections 11.0. */ +@NullUnmarked @SuppressWarnings("CanIgnoreReturnValueSuggester") public abstract class ConcurrentHashMapAcceptanceTest { private ExecutorService 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 index 8dc625538e..41e3047533 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.truth.Truth.assertAbout; +import static java.util.Objects.requireNonNull; import java.util.Collection; import java.util.Deque; @@ -25,6 +26,8 @@ import java.util.Queue; import java.util.Set; +import org.jspecify.annotations.Nullable; + import com.google.common.truth.FailureMetadata; import com.google.common.truth.IterableSubject; @@ -34,9 +37,9 @@ * @author ben.manes@gmail.com (Ben Manes) */ public class CollectionSubject extends IterableSubject { - private final Collection actual; + private final @Nullable Collection actual; - public CollectionSubject(FailureMetadata metadata, Collection subject) { + public CollectionSubject(FailureMetadata metadata, @Nullable Collection subject) { super(metadata, subject); this.actual = subject; } @@ -56,6 +59,7 @@ public final void hasSize(long expectedSize) { /** Fails if the collection does not have less than the given size. */ public void hasSizeLessThan(long other) { + requireNonNull(actual); checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other); check("size()").that(actual.size()).isLessThan(Math.toIntExact(other)); } @@ -83,11 +87,13 @@ public void isExhaustivelyEmpty() { } private void checkIterable() { + requireNonNull(actual); check("iterator().hasNext()").that(actual.iterator().hasNext()).isFalse(); } @SuppressWarnings("CollectionToArray") private void checkCollection() { + requireNonNull(actual); check("size()").that(actual).hasSize(0); check("isEmpty()").that(actual).isEmpty(); check("toArray()").that(actual.toArray()).isEmpty(); 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 index d9272f94ed..0c88752773 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java @@ -16,11 +16,14 @@ package com.github.benmanes.caffeine.testing; import static com.google.common.truth.Truth.assertAbout; +import static java.util.Objects.requireNonNull; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import org.jspecify.annotations.Nullable; + import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; import com.google.common.truth.ThrowableSubject; @@ -32,9 +35,9 @@ * @author ben.manes@gmail.com (Ben Manes) */ public final class FutureSubject extends Subject { - private final CompletableFuture actual; + private final @Nullable CompletableFuture actual; - private FutureSubject(FailureMetadata metadata, CompletableFuture subject) { + private FutureSubject(FailureMetadata metadata, @Nullable CompletableFuture subject) { super(metadata, subject); this.actual = subject; } @@ -43,12 +46,13 @@ public static Factory> future() { return FutureSubject::new; } - public static FutureSubject assertThat(CompletableFuture actual) { + public static FutureSubject assertThat(@Nullable CompletableFuture actual) { return assertAbout(future()).that(actual); } /** Fails if the future is not done. */ public void isDone() { + requireNonNull(actual); if (!actual.isDone()) { failWithActual("expected to be done", actual); } @@ -56,6 +60,7 @@ public void isDone() { /** Fails if the future is done. */ public void isNotDone() { + requireNonNull(actual); if (actual.isDone()) { failWithActual("expected to not be done", actual); } @@ -63,6 +68,7 @@ public void isNotDone() { /** Fails if the future is has not completed exceptionally. */ public void hasCompletedExceptionally() { + requireNonNull(actual); if (!actual.isCompletedExceptionally()) { failWithActual("expected to be completed exceptionally", actual.join()); } @@ -70,6 +76,7 @@ public void hasCompletedExceptionally() { /** Fails if the future is not successful with the given value. */ public void succeedsWith(int value) { + requireNonNull(actual); var result = actual.join(); if (result instanceof Int) { check("future").that(result).isEqualTo(Int.valueOf(value)); @@ -79,18 +86,21 @@ public void succeedsWith(int value) { } /** Fails if the future is not successful with the given value. */ - public void succeedsWith(Object value) { + public void succeedsWith(@Nullable Object value) { + requireNonNull(actual); check("future").that(actual.join()).isEqualTo(value); } /** Fails if the future is not successful with a null value. */ public void succeedsWithNull() { + requireNonNull(actual); check("future").that(actual.join()).isNull(); } /** Fails if the future is did not fail with the given join() exception. */ @CanIgnoreReturnValue public ThrowableSubject failsWith(Class clazz) { + requireNonNull(actual); try { failWithActual("join", actual.join()); throw new AssertionError(); 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 89da30ee3d..92ecba261e 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 @@ -16,6 +16,7 @@ package com.github.benmanes.caffeine.testing; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; import java.io.Serializable; import java.util.ArrayList; @@ -26,6 +27,8 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; +import org.jspecify.annotations.Nullable; + import com.google.errorprone.annotations.Immutable; /** @@ -48,8 +51,8 @@ public final class Int implements Serializable { private final int value; /** Constructs a newly allocated {@code Int} object with the same {@code value}. */ - public Int(Int value) { - this(value.value); + public Int(@Nullable Int value) { + this(requireNonNull(value).value); } /** 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 index 9d034d545e..d53e9a0f67 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java @@ -17,6 +17,8 @@ import static com.google.common.truth.Truth.assertAbout; +import org.jspecify.annotations.Nullable; + import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; @@ -27,7 +29,7 @@ */ public final class IntSubject extends Subject { - private IntSubject(FailureMetadata metadata, Int subject) { + private IntSubject(FailureMetadata metadata, @Nullable Int subject) { super(metadata, subject); } @@ -35,7 +37,7 @@ public static Factory integer() { return IntSubject::new; } - public static IntSubject assertThat(Int actual) { + public static IntSubject assertThat(@Nullable Int actual) { return assertAbout(integer()).that(actual); } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java index 4b793c3624..cd1b19bcc3 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/LoggingEvents.java @@ -25,6 +25,7 @@ import java.util.Objects; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.slf4j.event.Level; import com.github.valfirst.slf4jtest.LoggingEvent; @@ -43,7 +44,8 @@ public final class LoggingEvents extends ForwardingList { private final List> predicates; private final ImmutableList events; - private ImmutableList filteredEvents; + private @Nullable ImmutableList filteredEvents; + private boolean exclusive; private LoggingEvents(Iterable events) { 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 index 32c767e4da..edcffa2501 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java @@ -18,9 +18,12 @@ 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; +import static java.util.Objects.requireNonNull; import java.util.Map; +import org.jspecify.annotations.Nullable; + import com.google.common.collect.ImmutableSet; import com.google.common.collect.Range; import com.google.common.truth.FailureMetadata; @@ -34,9 +37,9 @@ */ public class MapSubject extends com.google.common.truth.MapSubject { @SuppressWarnings("ImmutableMemberCollection") - private final Map actual; + private final @Nullable Map actual; - public MapSubject(FailureMetadata metadata, Map subject) { + public MapSubject(FailureMetadata metadata, @Nullable Map subject) { super(metadata, subject); this.actual = subject; } @@ -56,29 +59,34 @@ public final void hasSize(long expectedSize) { /** Fails if the map does not have less than the given size. */ public void hasSizeLessThan(long other) { + requireNonNull(actual); checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other); check("size()").that(actual.size()).isLessThan(Math.toIntExact(other)); } /** Fails if the map's size is not in {@code range}. */ public void hasSizeIn(Range range) { + requireNonNull(actual); check("size()").that(actual.size()).isIn(range); } /** Fails if the map does not contain the given keys, where duplicate keys are ignored. */ @CanIgnoreReturnValue public Ordered containsExactlyKeys(Iterable keys) { + requireNonNull(actual); return check("containsKeys").that(actual.keySet()) .containsExactlyElementsIn(ImmutableSet.copyOf(keys)); } /** Fails if the map does not contain the given value. */ - public void containsValue(Object value) { + public void containsValue(@Nullable Object value) { + requireNonNull(actual); check("containsValue").that(actual.values()).contains(value); } /** Fails if the map does contain the given value. */ public void doesNotContainValue(Object value) { + requireNonNull(actual); check("containsValue").that(actual.values()).doesNotContain(value); } @@ -87,6 +95,7 @@ public void doesNotContainValue(Object value) { * methods. */ public void isExhaustivelyEmpty() { + requireNonNull(actual); isEqualTo(Map.of()); hasSize(0); isEmpty(); diff --git a/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml b/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml index 8ccad706c3..85ee0daab6 100644 --- a/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml +++ b/examples/coalescing-bulkloader-reactor/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] caffeine = "3.1.8" junit = "5.11.3" -reactor = "3.7.0" +reactor = "3.7.1" truth = "1.4.4" versions = "0.51.0" diff --git a/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties b/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties index 0cdabcd31b..c085078342 100644 --- a/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties +++ b/examples/coalescing-bulkloader-reactor/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/examples/coalescing-bulkloader-reactor/settings.gradle.kts b/examples/coalescing-bulkloader-reactor/settings.gradle.kts index 149babf6c3..53023a7268 100644 --- a/examples/coalescing-bulkloader-reactor/settings.gradle.kts +++ b/examples/coalescing-bulkloader-reactor/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/graal-native/gradle/wrapper/gradle-wrapper.properties b/examples/graal-native/gradle/wrapper/gradle-wrapper.properties index 0cdabcd31b..c085078342 100644 --- a/examples/graal-native/gradle/wrapper/gradle-wrapper.properties +++ b/examples/graal-native/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/examples/graal-native/settings.gradle.kts b/examples/graal-native/settings.gradle.kts index a5e4cd278c..1bb26f570f 100644 --- a/examples/graal-native/settings.gradle.kts +++ b/examples/graal-native/settings.gradle.kts @@ -5,7 +5,7 @@ pluginManagement { } } plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/hibernate/gradle/wrapper/gradle-wrapper.properties b/examples/hibernate/gradle/wrapper/gradle-wrapper.properties index 0cdabcd31b..c085078342 100644 --- a/examples/hibernate/gradle/wrapper/gradle-wrapper.properties +++ b/examples/hibernate/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/examples/hibernate/settings.gradle.kts b/examples/hibernate/settings.gradle.kts index 8a0dbec0f9..a6fd48777d 100644 --- a/examples/hibernate/settings.gradle.kts +++ b/examples/hibernate/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/indexable/gradle/wrapper/gradle-wrapper.properties b/examples/indexable/gradle/wrapper/gradle-wrapper.properties index 0cdabcd31b..c085078342 100644 --- a/examples/indexable/gradle/wrapper/gradle-wrapper.properties +++ b/examples/indexable/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/examples/indexable/settings.gradle.kts b/examples/indexable/settings.gradle.kts index e17d97cef9..58d363260e 100644 --- a/examples/indexable/settings.gradle.kts +++ b/examples/indexable/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties b/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties index 0cdabcd31b..c085078342 100644 --- a/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties +++ b/examples/resilience-failsafe/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/examples/resilience-failsafe/settings.gradle.kts b/examples/resilience-failsafe/settings.gradle.kts index 0ddb809517..090e9af4ed 100644 --- a/examples/resilience-failsafe/settings.gradle.kts +++ b/examples/resilience-failsafe/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties b/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties index 0cdabcd31b..c085078342 100644 --- a/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties +++ b/examples/write-behind-rxjava/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/examples/write-behind-rxjava/settings.gradle.kts b/examples/write-behind-rxjava/settings.gradle.kts index d04c4ad195..c288943379 100644 --- a/examples/write-behind-rxjava/settings.gradle.kts +++ b/examples/write-behind-rxjava/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fa913b441d..450e71ecd9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,14 +7,14 @@ bnd = "7.1.0" bouncycastle-jdk18on = "1.79" cache2k = "2.6.1.Final" caffeine = "3.1.8" -checkstyle = "10.20.2" +checkstyle = "10.21.0" coherence = "24.09" commons-collections4 = "4.4" commons-compress = "1.27.1" commons-io = "2.18.0" commons-lang3 = "3.17.0" commons-math3 = "3.6.1" -commons-text = "1.12.0" +commons-text = "1.13.0" concurrentlinkedhashmap = "1.4.2" config = "1.4.3" coveralls = "2.12.2" @@ -32,7 +32,7 @@ felix-scr = "2.2.12" findsecbugs = "1.13.0" flip-tables = "1.1.1" forbidden-apis = "3.8" -google-java-format = "1.25.0" +google-java-format = "1.25.2" guava = "33.3.1-jre" guice = "6.0.0" h2 = "2.3.232" @@ -67,8 +67,9 @@ jvm-dependency-conflict-resolution = "2.1.2" kotlin = "2.1.0" lincheck = "2.34" mockito = "5.14.2" +mockito-testng = "0.5.2" nexus-publish = "2.0.0" -nullaway = "0.12.1" +nullaway = "0.12.2" nullaway-plugin = "2.1.0" okhttp-bom = "4.12.0" okio-bom = "3.9.1" @@ -85,7 +86,7 @@ slf4j-test = "3.0.1" snakeyaml = "2.3" sonarqube = "6.0.1.5171" spotbugs = "4.8.6" -spotbugs-contrib = "7.6.8" +spotbugs-contrib = "7.6.9" spotbugs-plugin = "6.0.26" stream = "2.9.8" tcache = "2.0.1" @@ -171,6 +172,7 @@ junit5-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.re kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } lincheck = { module = "org.jetbrains.kotlinx:lincheck-jvm", version.ref = "lincheck" } mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-testng = { module = "org.mockito:mockito-testng", version.ref = "mockito-testng" } nullaway = { module = "com.uber.nullaway:nullaway", version.ref = "nullaway" } okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp-bom" } okio-bom = { module = "com.squareup.okio:okio-bom", version.ref = "okio-bom" } @@ -211,6 +213,7 @@ constraints = ["bcel", "bouncycastle-jdk18on", "commons-compress", "commons-text errorprone-support = ["errorprone-support", "errorprone-support-refaster"] junit = ["junit4", "junit5"] junit-engines = ["junit5-vintage", "junit5-testng"] +mockito = ["mockito", "mockito-testng"] osgi-test-compile = ["pax-exam-junit4"] osgi-test-runtime = ["felix-framework", "felix-scr", "osgi-function", "osgi-promise", "pax-exam-container-native", "pax-exam-link-mvn", "pax-url-aether"] diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts index 613c7e28ec..c8e64b1f28 100644 --- a/gradle/plugins/settings.gradle.kts +++ b/gradle/plugins/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } diff --git a/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts b/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts index 56f34e130a..addf43db7e 100644 --- a/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts +++ b/gradle/plugins/src/main/kotlin/lifecycle/testing.caffeine.gradle.kts @@ -1,9 +1,12 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED +import net.ltgt.gradle.errorprone.errorprone +import net.ltgt.gradle.nullaway.nullaway plugins { `java-library` + id("errorprone.caffeine") } val mockitoAgent: Configuration by configurations.creating @@ -13,11 +16,11 @@ dependencies { testImplementation(libs.guice) testImplementation(libs.truth) testImplementation(libs.testng) - testImplementation(libs.mockito) testImplementation(libs.hamcrest) testImplementation(libs.awaitility) testImplementation(libs.bundles.junit) testImplementation(libs.guava.testlib) + testImplementation(libs.bundles.mockito) testImplementation(libs.bundles.osgi.test.compile) testImplementation(platform(libs.asm.bom)) @@ -52,3 +55,17 @@ tasks.withType().configureEach { showCauses = true } } + +tasks.named("compileTestJava").configure { + options.errorprone.nullaway { + customInitializerAnnotations.addAll(listOf( + "org.testng.annotations.BeforeClass", + "org.testng.annotations.BeforeMethod")) + externalInitAnnotations.addAll(listOf( + "org.mockito.testng.MockitoSettings", + "picocli.CommandLine.Command")) + excludedFieldAnnotations.addAll(listOf( + "jakarta.inject.Inject", + "org.mockito.Mock")) + } +} diff --git a/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts b/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts index 115c71bedc..a71aa47639 100644 --- a/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts +++ b/gradle/plugins/src/main/kotlin/quality/errorprone.caffeine.gradle.kts @@ -66,6 +66,8 @@ tasks.withType().configureEach { nullaway { if (name.contains("Test")) { disable() + } else { + error() } annotatedPackages.add("com.github.benmanes.caffeine") annotatedPackages.add("com.google.common") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0cdabcd31b..c085078342 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-rc-1-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java index d277676bd0..e46a6a687d 100644 --- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java +++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventDispatcherTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.quality.Strictness.STRICT_STUBS; import java.util.ArrayList; import java.util.List; @@ -45,9 +46,10 @@ import javax.cache.event.CacheEntryUpdatedListener; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.testng.MockitoSettings; +import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.google.common.collect.Iterables; @@ -57,6 +59,9 @@ /** * @author ben.manes@gmail.com (Ben Manes) */ +@Test(singleThreaded = true) +@Listeners(MockitoTestNGListener.class) +@MockitoSettings(strictness = STRICT_STUBS) public final class EventDispatcherTest { @Mock Cache cache; @Mock CacheEntryCreatedListener createdListener; @@ -69,11 +74,6 @@ public final class EventDispatcherTest { CacheEntryEventFilter allowFilter = event -> true; CacheEntryEventFilter rejectFilter = event -> false; - @BeforeMethod - public void beforeMethod() throws Exception { - MockitoAnnotations.openMocks(this).close(); - } - @AfterTest public void afterTest() { executorService.shutdownNow(); diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java index 55174b595f..bf200bab5d 100644 --- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java +++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListenerTest.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.STRICT_STUBS; import java.io.IOException; @@ -32,23 +33,22 @@ import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; +import org.mockito.testng.MockitoSettings; +import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; /** * @author ben.manes@gmail.com (Ben Manes) */ +@Test(singleThreaded = true) @SuppressWarnings("unchecked") +@Listeners(MockitoTestNGListener.class) +@MockitoSettings(strictness = STRICT_STUBS) public final class EventTypeAwareListenerTest { @Mock Cache cache; - @BeforeMethod - public void before() throws Exception { - MockitoAnnotations.openMocks(this).close(); - } - @Test public void closed() throws IOException { when(cache.isClosed()).thenReturn(true); diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java index 1cc67e0504..4a6b6e0a8a 100644 --- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java +++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilterTest.java @@ -19,7 +19,7 @@ import javax.cache.event.CacheEntryCreatedListener; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** @@ -28,7 +28,7 @@ public final class EventTypeFilterTest { EventTypeFilter filter; - @BeforeTest + @BeforeClass public void before() { CacheEntryCreatedListener created = events -> {}; filter = new EventTypeFilter<>(created, event -> true); diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java index 3112441b48..2063ee9697 100644 --- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java +++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEntryEventTest.java @@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.quality.Strictness.STRICT_STUBS; import java.util.Iterator; import java.util.Map; @@ -26,8 +27,10 @@ import javax.cache.event.EventType; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeTest; +import org.mockito.testng.MockitoSettings; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.google.common.collect.testing.IteratorFeature; @@ -36,14 +39,16 @@ /** * @author ben.manes@gmail.com (Ben Manes) */ +@Test(singleThreaded = true) +@Listeners(MockitoTestNGListener.class) +@MockitoSettings(strictness = STRICT_STUBS) public final class JCacheEntryEventTest { @Mock Cache cache; JCacheEntryEvent event; - @BeforeTest - public void before() throws Exception { - MockitoAnnotations.openMocks(this).close(); + @BeforeMethod + public void before() { event = new JCacheEntryEvent<>(cache, EventType.CREATED, 1, /* hasOldValue= */ true, 2, 3); } diff --git a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java index 174ddf9c75..44db10fc59 100644 --- a/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java +++ b/jcache/src/test/java/com/github/benmanes/caffeine/jcache/event/JCacheEvictionListenerTest.java @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.quality.Strictness.STRICT_STUBS; import java.util.Arrays; import java.util.Iterator; @@ -29,9 +30,11 @@ import javax.cache.event.CacheEntryRemovedListener; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.testng.MockitoSettings; +import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.github.benmanes.caffeine.cache.RemovalCause; @@ -41,6 +44,9 @@ /** * @author ben.manes@gmail.com (Ben Manes) */ +@Test(singleThreaded = true) +@Listeners(MockitoTestNGListener.class) +@MockitoSettings(strictness = STRICT_STUBS) public final class JCacheEvictionListenerTest { JCacheEvictionListener listener; JCacheStatisticsMXBean statistics; @@ -49,8 +55,7 @@ public final class JCacheEvictionListenerTest { @Mock Cache cache; @BeforeMethod - public void before() throws Exception { - MockitoAnnotations.openMocks(this).close(); + public void before() { statistics = new JCacheStatisticsMXBean(); var dispatcher = new EventDispatcher(Runnable::run); listener = new JCacheEvictionListener<>(dispatcher, statistics); diff --git a/settings.gradle.kts b/settings.gradle.kts index cab1d2d545..451101b3ab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,9 +2,9 @@ pluginManagement { includeBuild("gradle/plugins") } plugins { - id("com.gradle.develocity") version "3.18.2" + id("com.gradle.develocity") version "3.19" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.2" - id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" } dependencyResolutionManagement {