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 ba4bc6c2dc..5ff31b1c03 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 @@ -698,8 +698,8 @@ boolean hasExpired(Node node, long now) { } /** - * Attempts to evict the entry based on the given removal cause. A removal due to expiration may - * be ignored if the entry was since updated and is no longer eligible for eviction. + * Attempts to evict the entry based on the given removal cause. A removal due to expiration or + * size may be ignored if the entry was since updated and is no longer eligible for eviction. * * @param node the entry to evict * @param cause the reason to evict @@ -1811,7 +1811,9 @@ V doComputeIfAbsent(K key, Object keyRef, Function mappi }); if (node == null) { - afterWrite(null, new RemovalTask(removed[0]), now); + if (removed[0] != null) { + afterWrite(null, new RemovalTask(removed[0]), now); + } return null; } if (cause[0] != null) { @@ -1978,6 +1980,8 @@ V remap(K key, Object keyRef, BiFunction rema if (removed[0] != null) { afterWrite(removed[0], new RemovalTask(removed[0]), now); + } else if (node == null) { + // absent and not computable } else if ((oldValue[0] == null) && (cause[0] == null)) { afterWrite(node, new AddTask(node, weight[1]), now); } else { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java index 4f17226990..6e89c619bb 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java @@ -1098,7 +1098,7 @@ public void compute_nullKey(Map map, CacheContext context) { @CacheSpec(removalListener = { Listener.DEFAULT, Listener.REJECTING }) @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) public void compute_nullMappingFunction(Map map, CacheContext context) { - map.computeIfPresent(1, null); + map.compute(1, null); } @CheckNoWriter @@ -1158,6 +1158,17 @@ public void compute_error(Map map, CacheContext context) { assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); } + @CheckNoWriter + @Test(dataProvider = "caches") + @CacheSpec(removalListener = { Listener.DEFAULT, Listener.REJECTING }) + public void compute_absent_nullValue(Map map, CacheContext context) { + assertThat(map.compute(context.absentKey(), (key, value) -> null), is(nullValue())); + assertThat(context, both(hasMissCount(0)).and(hasHitCount(0))); + assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); + assertThat(map.get(context.absentKey()), is(nullValue())); + assertThat(map.size(), is(context.original().size())); + } + @CheckNoWriter @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DEFAULT, Listener.REJECTING }) diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java index d50f006988..11647ae871 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java @@ -37,7 +37,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; @@ -176,8 +175,7 @@ public void getFunc_absent_null_async(AsyncLoadingCache cache, ready.set(true); Awaits.await().untilTrue(done); - MoreExecutors.shutdownAndAwaitTermination( - (ExecutorService) context.executor(), 1, TimeUnit.MINUTES); + MoreExecutors.shutdownAndAwaitTermination(context.executor(), 1, TimeUnit.MINUTES); assertThat(context, both(hasMissCount(1)).and(hasHitCount(0))); assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); @@ -216,8 +214,7 @@ public void getFunc_absent_failure_async(AsyncLoadingCache cac ready.set(true); Awaits.await().untilTrue(done); - MoreExecutors.shutdownAndAwaitTermination( - (ExecutorService) context.executor(), 1, TimeUnit.MINUTES); + MoreExecutors.shutdownAndAwaitTermination(context.executor(), 1, TimeUnit.MINUTES); assertThat(context, both(hasMissCount(1)).and(hasHitCount(0))); assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); @@ -240,8 +237,7 @@ public void getFunc_absent_cancelled(AsyncLoadingCache cache, valueFuture.cancel(true); Awaits.await().untilTrue(done); - MoreExecutors.shutdownAndAwaitTermination( - (ExecutorService) context.executor(), 1, TimeUnit.MINUTES); + MoreExecutors.shutdownAndAwaitTermination(context.executor(), 1, TimeUnit.MINUTES); assertThat(context, both(hasMissCount(1)).and(hasHitCount(0))); assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); @@ -445,8 +441,7 @@ public void get_absent_failure_async(AsyncLoadingCache cache, valueFuture.whenComplete((r, e) -> done.set(true)); Awaits.await().untilTrue(done); - MoreExecutors.shutdownAndAwaitTermination( - (ExecutorService) context.executor(), 1, TimeUnit.MINUTES); + MoreExecutors.shutdownAndAwaitTermination(context.executor(), 1, TimeUnit.MINUTES); assertThat(context, both(hasMissCount(1)).and(hasHitCount(0))); assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); 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 4748ff864f..249e352058 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 @@ -94,7 +94,7 @@ public void putWeighted_noOverflow() { @Test(dataProvider = "caches") @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, - population = Population.FULL, maximumSize = MaximumSize.FULL, + population = Population.FULL, maximumSize = MaximumSize.FULL, executorMayFail = true, executor = CacheExecutor.REJECTING, removalListener = Listener.CONSUMING) public void evict_rejected(Cache cache, CacheContext context) { cache.put(context.absentKey(), context.absentValue()); @@ -283,7 +283,7 @@ public void exceedsMaximumBufferSize_onRead(Cache cache, Cache @Test(dataProvider = "caches") @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = MaximumSize.FULL) - public void exceedsMaximumBufferSize_onWrite(Cache cache) { + public void exceedsMaximumBufferSize_onWrite(Cache cache, CacheContext context) { BoundedLocalCache localCache = asBoundedLocalCache(cache); Node dummy = localCache.nodeFactory.newNode(null, null, null, 1, 0); @@ -383,7 +383,7 @@ public void drain_onWrite(Cache cache, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = MaximumSize.FULL) - public void drain_nonblocking(Cache cache) { + public void drain_nonblocking(Cache cache, CacheContext context) { BoundedLocalCache localCache = asBoundedLocalCache(cache); AtomicBoolean done = new AtomicBoolean(); Runnable task = () -> { @@ -403,7 +403,7 @@ public void drain_nonblocking(Cache cache) { @Test(dataProvider = "caches") @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = MaximumSize.FULL) - public void drain_blocksClear(Cache cache) { + public void drain_blocksClear(Cache cache, CacheContext context) { BoundedLocalCache localCache = asBoundedLocalCache(cache); checkDrainBlocks(localCache, localCache::clear); } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java index 362f731c3a..7a0a4d4ea9 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java @@ -151,6 +151,8 @@ public void get_throwsException(Cache cache, CacheContext cont @Test(dataProvider = "caches") public void get_absent_null(LoadingCache cache, CacheContext context) { assertThat(cache.get(context.absentKey(), k -> null), is(nullValue())); + assertThat(context, both(hasMissCount(1)).and(hasHitCount(0))); + assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); } @CacheSpec 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 35b25acbd0..806da9f026 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 @@ -122,7 +122,7 @@ public void evict(Cache cache, CacheContext context, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = MaximumSize.TEN, weigher = CacheWeigher.COLLECTION, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void evict_weighted(Cache> cache, - Eviction eviction, CacheContext context) { + CacheContext context, Eviction eviction) { @SuppressWarnings({"unchecked", "rawtypes"}) CacheWriter> writer = (CacheWriter) context.cacheWriter(); @@ -256,7 +256,8 @@ public void put_zeroWeight(Cache cache, CacheContext context) @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void put(Cache> cache, Eviction eviction) { + public void put(Cache> cache, + CacheContext context, Eviction eviction) { cache.put("a", asList(1, 2, 3)); assertThat(cache.estimatedSize(), is(1L)); assertThat(eviction.weightedSize().getAsLong(), is(3L)); @@ -266,7 +267,8 @@ public void put(Cache> cache, Eviction eviction) { @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void put_sameWeight(Cache> cache, Eviction eviction) { + public void put_sameWeight(Cache> cache, + CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.put("a", asList(-1, -2, -3)); @@ -279,7 +281,7 @@ public void put_sameWeight(Cache> cache, Eviction ev weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void put_changeWeight(Cache> cache, - Eviction eviction, CacheContext context) { + CacheContext context, Eviction eviction) { @SuppressWarnings({"unchecked", "rawtypes"}) CacheWriter> writer = (CacheWriter) context.cacheWriter(); @@ -324,7 +326,8 @@ public void put_asyncWeight(AsyncLoadingCache> cache, @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void replace_sameWeight(Cache> cache, Eviction eviction) { + public void replace_sameWeight(Cache> cache, + CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.asMap().replace("a", asList(-1, -2, -3)); @@ -336,7 +339,8 @@ public void replace_sameWeight(Cache> cache, Eviction> cache, Eviction eviction) { + public void replace_changeWeight(Cache> cache, + CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.asMap().replace("a", asList(-1, -2, -3, -4)); @@ -349,7 +353,7 @@ public void replace_changeWeight(Cache> cache, Eviction> cache, Eviction eviction) { + Cache> cache, CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().replace("a", asList(1, 2, 3), asList(4, 5, 6)), is(true)); @@ -362,7 +366,7 @@ public void replaceConditionally_sameWeight( weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void replaceConditionally_changeWeight( - Cache> cache, Eviction eviction) { + Cache> cache, CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.asMap().replace("a", asList(1, 2, 3), asList(-1, -2, -3, -4)); @@ -375,7 +379,7 @@ public void replaceConditionally_changeWeight( weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void replaceConditionally_fails( - Cache> cache, Eviction eviction) { + Cache> cache, CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().replace("a", asList(1), asList(4, 5)), is(false)); @@ -387,7 +391,8 @@ public void replaceConditionally_fails( @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void remove(Cache> cache, Eviction eviction) { + public void remove(Cache> cache, + CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().remove("a"), is(asList(1, 2, 3))); @@ -399,7 +404,8 @@ public void remove(Cache> cache, Eviction eviction) @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void removeConditionally(Cache> cache, Eviction eviction) { + public void removeConditionally(Cache> cache, + CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().remove("a", asList(1, 2, 3)), is(true)); @@ -412,7 +418,7 @@ public void removeConditionally(Cache> cache, Eviction> cache, Eviction eviction) { + Cache> cache, CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().remove("a", asList(-1, -2, -3)), is(false)); @@ -424,7 +430,8 @@ public void removeConditionally_fails( @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void invalidateAll(Cache> cache, Eviction eviction) { + public void invalidateAll(Cache> cache, + CacheContext context, Eviction eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.invalidateAll(); @@ -446,7 +453,8 @@ public void isWeighted(CacheContext context, Eviction eviction @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, weigher = CacheWeigher.TEN) - public void weightedSize(Cache cache, Eviction eviction) { + public void weightedSize(Cache cache, CacheContext context, + Eviction eviction) { assertThat(eviction.weightedSize().getAsLong(), is(10 * cache.estimatedSize())); } @@ -455,8 +463,8 @@ public void weightedSize(Cache cache, Eviction cache, CacheContext context, - Eviction eviction) { + public void maximumSize_decrease(Cache cache, + CacheContext context, Eviction eviction) { long newSize = context.maximumWeightOrSize() / 2; eviction.setMaximum(newSize); assertThat(eviction.getMaximum(), is(newSize)); @@ -474,8 +482,8 @@ public void maximumSize_decrease(Cache cache, CacheContext con @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, removalListener = { Listener.DEFAULT, Listener.CONSUMING }) - public void maximumSize_decrease_min(Cache cache, CacheContext context, - Eviction eviction) { + public void maximumSize_decrease_min(Cache cache, + CacheContext context, Eviction eviction) { eviction.setMaximum(0); assertThat(eviction.getMaximum(), is(0L)); if (context.initialSize() > 0) { @@ -488,8 +496,8 @@ public void maximumSize_decrease_min(Cache cache, CacheContext @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, removalListener = { Listener.DEFAULT, Listener.CONSUMING }) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void maximumSize_decrease_negative(Cache cache, CacheContext context, - Eviction eviction) { + public void maximumSize_decrease_negative(Cache cache, + CacheContext context, Eviction eviction) { try { eviction.setMaximum(-1); } finally { @@ -501,8 +509,8 @@ public void maximumSize_decrease_negative(Cache cache, CacheCo @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, removalListener = { Listener.DEFAULT, Listener.REJECTING }) - public void maximumSize_increase(Cache cache, CacheContext context, - Eviction eviction) { + public void maximumSize_increase(Cache cache, + CacheContext context, Eviction eviction) { eviction.setMaximum(2 * context.maximumWeightOrSize()); assertThat(cache.estimatedSize(), is(context.initialSize())); assertThat(eviction.getMaximum(), is(2 * context.maximumWeightOrSize())); @@ -511,8 +519,8 @@ public void maximumSize_increase(Cache cache, CacheContext con @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL, removalListener = Listener.REJECTING) - public void maximumSize_increase_max(Cache cache, CacheContext context, - Eviction eviction) { + public void maximumSize_increase_max(Cache cache, + CacheContext context, Eviction eviction) { eviction.setMaximum(Long.MAX_VALUE); assertThat(cache.estimatedSize(), is(context.initialSize())); assertThat(eviction.getMaximum(), is(Long.MAX_VALUE - Integer.MAX_VALUE)); // impl detail @@ -522,19 +530,19 @@ public void maximumSize_increase_max(Cache cache, CacheContext @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void coldest_unmodifiable(Eviction eviction) { + public void coldest_unmodifiable(CacheContext context, Eviction eviction) { eviction.coldest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void coldest_negative(Eviction eviction) { + public void coldest_negative(CacheContext context, Eviction eviction) { eviction.coldest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL) - public void coldest_zero(Eviction eviction) { + public void coldest_zero(CacheContext context, Eviction eviction) { assertThat(eviction.coldest(0), is(emptyMap())); } @@ -580,19 +588,19 @@ public void coldest_snapshot(Cache cache, CacheContext context @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void hottest_unmodifiable(Eviction eviction) { + public void hottest_unmodifiable(CacheContext context, Eviction eviction) { eviction.hottest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void hottest_negative(Eviction eviction) { + public void hottest_negative(CacheContext context, Eviction eviction) { eviction.hottest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = MaximumSize.FULL) - public void hottest_zero(Eviction eviction) { + public void hottest_zero(CacheContext context, Eviction eviction) { assertThat(eviction.hottest(0), is(emptyMap())); } @@ -617,8 +625,8 @@ public void hottest_order(CacheContext context, Eviction evict @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = MaximumSize.FULL) - public void hottest_snapshot(Cache cache, CacheContext context, - Eviction eviction) { + public void hottest_snapshot(Cache cache, + CacheContext context, Eviction eviction) { Map hottest = eviction.hottest(Integer.MAX_VALUE); cache.invalidateAll(); assertThat(hottest, is(equalTo(context.original()))); diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java index 21253db44e..d515db43a3 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java @@ -87,7 +87,7 @@ public void expire_zero(Cache cache, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, writer = Writer.EXCEPTIONAL, requiresExpiration = true, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, executorMayFail = true, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, removalListener = Listener.REJECTING) public void getIfPresent_writerFails(Cache cache, CacheContext context) { @@ -837,7 +837,7 @@ public void computeIfPresent(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, requiresExpiration = true, executorMayFail = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java index 4fd9b05753..e44ef1af98 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java @@ -203,7 +203,7 @@ public void putIfAbsent(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) - public void getExpiresAfter( + public void getExpiresAfter(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { assertThat(expireAfterAccess.getExpiresAfter(TimeUnit.MINUTES), is(1L)); } @@ -236,20 +236,22 @@ public void ageOf(CacheContext context, @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void oldest_unmodifiable( + public void oldest_unmodifiable(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { expireAfterAccess.oldest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void oldest_negative(@ExpireAfterAccess Expiration expireAfterAccess) { + public void oldest_negative(CacheContext context, + @ExpireAfterAccess Expiration expireAfterAccess) { expireAfterAccess.oldest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) - public void oldest_zero(@ExpireAfterAccess Expiration expireAfterAccess) { + public void oldest_zero(CacheContext context, + @ExpireAfterAccess Expiration expireAfterAccess) { assertThat(expireAfterAccess.oldest(0), is(emptyMap())); } @@ -285,20 +287,22 @@ public void oldest_snapshot(Cache cache, CacheContext context, @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void youngest_unmodifiable( + public void youngest_unmodifiable(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { expireAfterAccess.youngest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void youngest_negative(@ExpireAfterAccess Expiration expireAfterAccess) { + public void youngest_negative(CacheContext context, + @ExpireAfterAccess Expiration expireAfterAccess) { expireAfterAccess.youngest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) - public void youngest_zero(@ExpireAfterAccess Expiration expireAfterAccess) { + public void youngest_zero(CacheContext context, + @ExpireAfterAccess Expiration expireAfterAccess) { assertThat(expireAfterAccess.youngest(0), is(emptyMap())); } 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 09be14b20e..6cfb69162d 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 @@ -190,7 +190,7 @@ public void putIfAbsent(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) - public void getExpiresAfter( + public void getExpiresAfter(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { assertThat(expireAfterWrite.getExpiresAfter(TimeUnit.MINUTES), is(1L)); } @@ -223,20 +223,22 @@ public void ageOf(CacheContext context, @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void oldest_unmodifiable( + public void oldest_unmodifiable(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { expireAfterWrite.oldest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void oldest_negative(@ExpireAfterWrite Expiration expireAfterWrite) { + public void oldest_negative(CacheContext context, + @ExpireAfterWrite Expiration expireAfterWrite) { expireAfterWrite.oldest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) - public void oldest_zero(@ExpireAfterWrite Expiration expireAfterWrite) { + public void oldest_zero(CacheContext context, + @ExpireAfterWrite Expiration expireAfterWrite) { assertThat(expireAfterWrite.oldest(0), is(emptyMap())); } @@ -272,20 +274,22 @@ public void oldest_snapshot(Cache cache, CacheContext context, @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void youngest_unmodifiable( + public void youngest_unmodifiable(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { expireAfterWrite.youngest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void youngest_negative(@ExpireAfterWrite Expiration expireAfterWrite) { + public void youngest_negative(CacheContext context, + @ExpireAfterWrite Expiration expireAfterWrite) { expireAfterWrite.youngest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) - public void youngest_zero(@ExpireAfterWrite Expiration expireAfterWrite) { + public void youngest_zero(CacheContext context, + @ExpireAfterWrite Expiration expireAfterWrite) { assertThat(expireAfterWrite.youngest(0), is(emptyMap())); } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java index b5c5f4f54c..e75c922085 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java @@ -71,6 +71,8 @@ public void get_null(LoadingCache cache, CacheContext context) @CacheSpec(loader = Loader.NULL) public void get_absent_null(LoadingCache cache, CacheContext context) { assertThat(cache.get(context.absentKey()), is(nullValue())); + assertThat(context, both(hasMissCount(1)).and(hasHitCount(0))); + assertThat(context, both(hasLoadSuccessCount(0)).and(hasLoadFailureCount(1))); } @CheckNoWriter 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 b166f3e9bc..c83750bd4d 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 @@ -203,7 +203,7 @@ public void getAll(AsyncLoadingCache cache, CacheContext conte @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE) - public void getExpiresAfter( + public void getExpiresAfter(CacheContext context, @RefreshAfterWrite Expiration refreshAfterWrite) { assertThat(refreshAfterWrite.getExpiresAfter(TimeUnit.MINUTES), is(1L)); } @@ -232,20 +232,22 @@ public void ageOf(CacheContext context, @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void oldest_unmodifiable( + public void oldest_unmodifiable(CacheContext context, @RefreshAfterWrite Expiration refreshAfterWrite) { refreshAfterWrite.oldest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void oldest_negative(@RefreshAfterWrite Expiration refreshAfterWrite) { + public void oldest_negative(CacheContext context, + @RefreshAfterWrite Expiration refreshAfterWrite) { refreshAfterWrite.oldest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE) - public void oldest_zero(@RefreshAfterWrite Expiration refreshAfterWrite) { + public void oldest_zero(CacheContext context, + @RefreshAfterWrite Expiration refreshAfterWrite) { assertThat(refreshAfterWrite.oldest(0), is(emptyMap())); } @@ -281,20 +283,22 @@ public void oldest_snapshot(Cache cache, CacheContext context, @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void youngest_unmodifiable( + public void youngest_unmodifiable(CacheContext context, @RefreshAfterWrite Expiration refreshAfterWrite) { refreshAfterWrite.youngest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void youngest_negative(@RefreshAfterWrite Expiration refreshAfterWrite) { + public void youngest_negative(CacheContext context, + @RefreshAfterWrite Expiration refreshAfterWrite) { refreshAfterWrite.youngest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE) - public void youngest_zero(@RefreshAfterWrite Expiration refreshAfterWrite) { + public void youngest_zero(CacheContext context, + @RefreshAfterWrite Expiration refreshAfterWrite) { assertThat(refreshAfterWrite.youngest(0), is(emptyMap())); } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/UnboundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/UnboundedLocalCacheTest.java index 09b3935ee0..efeb09d394 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/UnboundedLocalCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/UnboundedLocalCacheTest.java @@ -23,6 +23,7 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; +import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; @@ -48,7 +49,7 @@ public final class UnboundedLocalCacheTest { refreshAfterWrite = Expire.DISABLED, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) @Test(dataProvider = "caches") - public void noPolicy(Cache cache) { + public void noPolicy(Cache cache, CacheContext context) { assertThat(cache.policy().eviction(), is(Optional.empty())); assertThat(cache.policy().expireAfterWrite(), is(Optional.empty())); assertThat(cache.policy().expireAfterAccess(), is(Optional.empty())); @@ -61,7 +62,7 @@ public void noPolicy(Cache cache) { refreshAfterWrite = Expire.DISABLED, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) @Test(dataProvider = "caches") - public void noPolicy_async(AsyncLoadingCache cache) { + public void noPolicy_async(AsyncLoadingCache cache, CacheContext context) { assertThat(cache.synchronous().policy().eviction(), is(Optional.empty())); assertThat(cache.synchronous().policy().expireAfterWrite(), is(Optional.empty())); assertThat(cache.synchronous().policy().expireAfterAccess(), is(Optional.empty())); 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 af03ed1d7f..9f988064a3 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 @@ -26,7 +26,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Executor; import java.util.concurrent.ThreadLocalRandom; import javax.annotation.Nullable; @@ -66,12 +65,12 @@ public final class CacheContext { final CacheExecutor cacheExecutor; final ReferenceType valueStrength; final ReferenceType keyStrength; + final TrackingExecutor executor; final MaximumSize maximumSize; final Population population; final CacheWeigher weigher; final Expire afterAccess; final Expire afterWrite; - final Executor executor; final Compute compute; final Advance advance; final Expire refresh; @@ -354,7 +353,7 @@ public CacheExecutor executorType() { return cacheExecutor; } - public Executor executor() { + public TrackingExecutor executor() { return executor; } 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 532fd7061f..fed326be07 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 @@ -25,10 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -483,36 +480,41 @@ CacheExecutor[] executor() default { CacheExecutor.DIRECT, }; + /** If the executor is allowed to have failures. */ + boolean executorMayFail() default false; + /** The executors that the cache can be configured with. */ - enum CacheExecutor implements Supplier { + enum CacheExecutor implements Supplier { DEFAULT { // fork-join common pool - @Override public Executor get() { + @Override public TrackingExecutor get() { // Use with caution as may be unpredictable during tests if awaiting completion - return ForkJoinPool.commonPool(); + return null; } }, DIRECT { - @Override public Executor get() { + @Override public TrackingExecutor get() { // Cache implementations must avoid deadlocks by incorrectly assuming async execution - return MoreExecutors.directExecutor(); + return new TrackingExecutor(MoreExecutors.newDirectExecutorService()); } }, SINGLE { - @Override public Executor get() { + @Override public TrackingExecutor get() { // Isolated to the test execution - may be shutdown by test to assert completion - return Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder().setDaemon(true).build()); + return new TrackingExecutor(Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setDaemon(true).build())); } }, REJECTING { - @Override public Executor get() { + @Override public TrackingExecutor get() { // Cache implementations must avoid corrupting internal state due to rejections - return runnable -> { throw new RejectedExecutionException(); }; + TrackingExecutor executor = CacheExecutor.DIRECT.get(); + executor.shutdown(); + return executor; } }; @Override - public abstract Executor get(); + public abstract TrackingExecutor get(); } /* ---------------- Populated -------------- */ diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java index 91704d69d8..171312ba48 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java @@ -24,6 +24,7 @@ import static com.github.benmanes.caffeine.cache.testing.HasStats.hasLoadSuccessCount; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasMissCount; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; @@ -67,6 +68,7 @@ public void afterInvocation(IInvokedMethod method, ITestResult testResult) { } checkWriter(testResult, context); checkNoStats(testResult, context); + checkExecutor(testResult, context); } } catch (Throwable caught) { testResult.setStatus(ITestResult.FAILURE); @@ -76,6 +78,23 @@ public void afterInvocation(IInvokedMethod method, ITestResult testResult) { } } + /** Checks whether the {@link TrackingExecutor} had unexpected failures. */ + private static void checkExecutor(ITestResult testResult, CacheContext context) { + Method testMethod = testResult.getMethod().getConstructorOrMethod().getMethod(); + CacheSpec cacheSpec = testMethod.getAnnotation(CacheSpec.class); + if (cacheSpec == null) { + return; + } + + assertThat("CacheContext required", context, is(not(nullValue()))); + TrackingExecutor executor = context.executor(); + if (cacheSpec.executorMayFail()) { + assertThat(executor.failureCount(), is(greaterThan(0))); + } else { + assertThat(executor.failureCount(), is(0)); + } + } + /** Checks the writer if {@link CheckNoWriter} is found. */ private static void checkWriter(ITestResult testResult, CacheContext context) { Method testMethod = testResult.getMethod().getConstructorOrMethod().getMethod(); @@ -83,6 +102,7 @@ private static void checkWriter(ITestResult testResult, CacheContext context) { if (checkWriter == null) { return; } + assertThat("Test requires CacheContext param for validation", context, is(not(nullValue()))); verifyWriter(context, (verifier, writer) -> verifier.zeroInteractions()); } 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 df369a199a..93eef21b2e 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 @@ -329,6 +329,7 @@ public V compute(K key, BiFunction remappingF statsCounter.recordLoadException(ticker.read() - now); return null; } else { + statsCounter.recordLoadException(ticker.read() - now); return null; } } else { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java new file mode 100644 index 0000000000..585444b47e --- /dev/null +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 Ben Manes. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.benmanes.caffeine.cache.testing; + +import static java.util.Objects.requireNonNull; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.util.concurrent.ForwardingExecutorService; + +/** + * An executor that retains metrics regarding the execution history. + * + * @author ben.manes@gmail.com (Ben Manes) + */ +public final class TrackingExecutor extends ForwardingExecutorService { + private final ExecutorService delegate; + private final AtomicInteger totalTasks; + private final AtomicInteger failures; + + public TrackingExecutor(ExecutorService executor) { + delegate = requireNonNull(executor); + totalTasks = new AtomicInteger(); + failures = new AtomicInteger(); + } + + @Override + public void execute(Runnable command) { + totalTasks.incrementAndGet(); + Runnable submit = makeFailureAware(() -> delegate.execute(makeFailureAware(command))); + submit.run(); + } + + private Runnable makeFailureAware(Runnable task) { + return () -> { + try { + task.run(); + } catch (Throwable t) { + failures.incrementAndGet(); + throw t; + } + }; + } + + public int totalTasksCount() { + return totalTasks.get(); + } + + public int failureCount() { + return failures.get(); + } + + @Override + protected ExecutorService delegate() { + return delegate; + } +} diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index fc415ebf44..44d313f367 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -40,7 +40,7 @@ ext { ycsb: '0.5.0', ] test_versions = [ - awaitility: '1.6.5', + awaitility: '1.7.0', easymock: '3.4', hamcrest: '2.0.0.0', jcache_tck: '1.0.1', @@ -58,8 +58,8 @@ ext { ehcache2: '2.10.1-55', ehcache3: '3.0.0.m4', high_scale_lib: '1.0.6', - infinispan: '8.1.0.Beta1', - jackrabbit: '1.3.10', + infinispan: '8.1.0.CR1', + jackrabbit: '1.3.11', jamm: '0.3.1', java_object_layout: '0.3.2', koloboke: '0.6.8', @@ -67,7 +67,7 @@ ext { tcache: '0.4.0', ] plugin_versions = [ - checkstyle: '6.7', + checkstyle: '6.13', coveralls: '2.4.0', extra_conf: '3.0.3', error_prone: '0.0.8',