diff --git a/build.gradle b/build.gradle index f6f8f5a111..5921faac8f 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { apply from: "${rootDir}/gradle/dependencies.gradle" repositories { + mavenCentral() jcenter() } @@ -26,6 +27,13 @@ task testReport(type: TestReport, group: 'Build') { destinationDir = file("${buildDir}/reports/allTests") } +allprojects { + repositories { + mavenCentral() + jcenter() + } +} + subprojects { proj -> if (proj.name == 'tracing') { return @@ -51,10 +59,6 @@ subprojects { proj -> } archivesBaseName = path[1..-1].replaceAll(':', '-').toLowerCase() - repositories { - jcenter() - } - configurations { testArtifacts } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/ConcurrentLinkedStack.java b/caffeine/src/main/java/com/github/benmanes/caffeine/ConcurrentLinkedStack.java index d096140303..c0333d6d6a 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/ConcurrentLinkedStack.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/ConcurrentLinkedStack.java @@ -51,10 +51,10 @@ * This implementation employs elimination and combination to transfer elements between threads that * are pushing and popping concurrently. This technique avoids contention on the stack by attempting * to cancel opposing operations and merge additive operations if an immediate update to the stack - * is not successful. When a pair of operations collide, the task of performing the combined set of - * operations is delegated to one of the threads and the other thread optionally waits for its - * operation to be completed. This decision of whether to wait for completion is determined by - * constructing either a linearizable or optimistic stack. + * is not successful. When a pair of additive operations collide, the task of performing the + * combined set of operations is delegated to one of the threads and the other thread optionally + * waits for its operation to be completed. This decision of whether to wait for completion is + * determined by constructing either a linearizable or optimistic stack. *

* Iterators are weakly consistent, returning elements reflecting the state of the stack at * some point at or since the creation of the iterator. They do not throw diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/SingleConsumerQueue.java b/caffeine/src/main/java/com/github/benmanes/caffeine/SingleConsumerQueue.java index 4f95755a18..b5e3e3d9bf 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/SingleConsumerQueue.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/SingleConsumerQueue.java @@ -446,7 +446,7 @@ Object readResolve() { } static class Node { - final static long NEXT_OFFSET = UnsafeAccess.objectFieldOffset(Node.class, "next"); + static final long NEXT_OFFSET = UnsafeAccess.objectFieldOffset(Node.class, "next"); E value; volatile Node next; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/ConcurrentLinkedStackTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/ConcurrentLinkedStackTest.java index 75a0d161b9..8457cb97a4 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/ConcurrentLinkedStackTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/ConcurrentLinkedStackTest.java @@ -50,6 +50,7 @@ import com.github.benmanes.caffeine.ConcurrentLinkedStack.Node; import com.github.benmanes.caffeine.ConcurrentLinkedStackTest.ValidatingStackListener; import com.github.benmanes.caffeine.base.UnsafeAccess; +import com.github.benmanes.caffeine.testing.Awaits; import com.google.common.collect.Iterables; import com.google.common.testing.SerializableTester; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidConcurrentLinkedStack.java b/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidConcurrentLinkedStack.java index 5525903f26..e1d4cd7737 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidConcurrentLinkedStack.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidConcurrentLinkedStack.java @@ -15,7 +15,7 @@ */ package com.github.benmanes.caffeine; -import static com.github.benmanes.caffeine.matchers.IsEmptyIterable.deeplyEmpty; +import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @@ -29,7 +29,7 @@ import org.hamcrest.TypeSafeDiagnosingMatcher; import com.github.benmanes.caffeine.ConcurrentLinkedStack.Node; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; import com.google.common.collect.Sets; /** diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidSingleConsumerQueue.java b/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidSingleConsumerQueue.java index f283600c38..b112391ef1 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidSingleConsumerQueue.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/IsValidSingleConsumerQueue.java @@ -15,7 +15,7 @@ */ package com.github.benmanes.caffeine; -import static com.github.benmanes.caffeine.matchers.IsEmptyIterable.deeplyEmpty; +import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -30,7 +30,7 @@ import org.hamcrest.TypeSafeDiagnosingMatcher; import com.github.benmanes.caffeine.SingleConsumerQueue.Node; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; import com.google.common.collect.Sets; /** diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/SingleConsumerQueueTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/SingleConsumerQueueTest.java index b23d470204..60c135f641 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/SingleConsumerQueueTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/SingleConsumerQueueTest.java @@ -16,7 +16,7 @@ package com.github.benmanes.caffeine; import static com.github.benmanes.caffeine.IsValidSingleConsumerQueue.validate; -import static com.github.benmanes.caffeine.matchers.IsEmptyIterable.deeplyEmpty; +import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; import static com.google.common.collect.Iterators.elementsEqual; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.arrayContaining; @@ -46,6 +46,7 @@ import com.github.benmanes.caffeine.SingleConsumerQueue.LinearizableNode; import com.github.benmanes.caffeine.SingleConsumerQueueTest.ValidatingQueueListener; +import com.github.benmanes.caffeine.testing.Awaits; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.testing.SerializableTester; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/StackMultiThreadedTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/StackMultiThreadedTest.java new file mode 100644 index 0000000000..91d77b1213 --- /dev/null +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/StackMultiThreadedTest.java @@ -0,0 +1,89 @@ +/* + * 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; + +import java.util.List; +import java.util.function.BiConsumer; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import com.github.benmanes.caffeine.ConcurrentLinkedStackTest.ValidatingStackListener; +import com.github.benmanes.caffeine.testing.Threads; +import com.google.common.collect.ImmutableList; +import com.google.common.testing.SerializableTester; + +import scala.concurrent.forkjoin.ThreadLocalRandom; + +/** + * @author ben.manes@gmail.com (Ben Manes) + */ +@Listeners(ValidatingStackListener.class) +public final class StackMultiThreadedTest { + + @Test(dataProvider = "queues") + public void thrash(ConcurrentLinkedStack stack) { + Threads.runTest(stack, operations); + } + + @DataProvider(name = "queues") + public Object[][] providesQueues() { + return new Object[][] { + { ConcurrentLinkedStack.optimistic() }, + { ConcurrentLinkedStack.linearizable() }}; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + List, Integer>> operations = ImmutableList.of( + // Stack + (c, e) -> c.push(e), + (c, e) -> c.pop(), + (c, e) -> c.peek(), + + // Queue + (c, e) -> c.asLifoQueue().offer(e), + (c, e) -> c.asLifoQueue().poll(), + (c, e) -> c.asLifoQueue().peek(), + + // Collection + (c, e) -> c.size(), + (c, e) -> c.isEmpty(), + (c, e) -> c.contains(e), + (c, e) -> c.containsAll(ImmutableList.of(e, e)), + (c, e) -> c.add(e), + (c, e) -> c.addAll(ImmutableList.of(e, e)), + (c, e) -> c.remove(e), + (c, e) -> c.removeAll(ImmutableList.of(e, e)), + (c, e) -> { // expensive so do it less frequently + int random = ThreadLocalRandom.current().nextInt(); + if ((random & 255) == 0) { + c.retainAll(ImmutableList.of(e, e)); + } + }, + (c, e) -> { // expensive so do it less frequently + int random = ThreadLocalRandom.current().nextInt(); + if ((random & 255) == 0) { + c.clear(); + } + }, + (c, e) -> { // expensive so do it less frequently + int random = ThreadLocalRandom.current().nextInt(); + if ((random & 255) == 0) { + SerializableTester.reserialize(c); + } + }); +} 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 b197e76757..e515f31c27 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 @@ -20,8 +20,8 @@ import static com.github.benmanes.caffeine.cache.testing.HasStats.hasLoadFailureCount; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasLoadSuccessCount; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasMissCount; -import static com.github.benmanes.caffeine.matchers.IsEmptyIterable.deeplyEmpty; -import static com.github.benmanes.caffeine.matchers.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; +import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static com.google.common.collect.Maps.immutableEntry; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.both; 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 1c532352c8..eb2319e6b8 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 @@ -21,7 +21,7 @@ import static com.github.benmanes.caffeine.cache.testing.HasStats.hasLoadFailureCount; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasLoadSuccessCount; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasMissCount; -import static com.github.benmanes.caffeine.matchers.IsFutureValue.futureOf; +import static com.github.benmanes.caffeine.testing.IsFutureValue.futureOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.equalTo; @@ -46,7 +46,6 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import com.github.benmanes.caffeine.Awaits; import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; @@ -56,6 +55,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheSpec.Loader; import com.github.benmanes.caffeine.cache.testing.CacheSpec.MaximumSize; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Population; +import com.github.benmanes.caffeine.testing.Awaits; import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; import com.github.benmanes.caffeine.cache.testing.CheckNoStats; import com.google.common.collect.ImmutableList; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncTest.java index 9eebc57a4e..44963c3c55 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncTest.java @@ -28,7 +28,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import com.github.benmanes.caffeine.Awaits; +import com.github.benmanes.caffeine.testing.Awaits; /** * @author ben.manes@gmail.com (Ben Manes) 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 8bfb7be3ce..05d789e1d3 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 @@ -34,7 +34,6 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import com.github.benmanes.caffeine.Awaits; import com.github.benmanes.caffeine.ConcurrentTestHarness; import com.github.benmanes.caffeine.cache.BoundedLocalCache.DrainStatus; import com.github.benmanes.caffeine.cache.Policy.Eviction; @@ -50,6 +49,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheSpec.Population; import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; import com.github.benmanes.caffeine.locks.NonReentrantLock; +import com.github.benmanes.caffeine.testing.Awaits; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; 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 af86c786d0..b884b09482 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 @@ -17,7 +17,7 @@ import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasEvictionCount; -import static com.github.benmanes.caffeine.matchers.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -37,7 +37,6 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import com.github.benmanes.caffeine.Awaits; import com.github.benmanes.caffeine.cache.Policy.Eviction; import com.github.benmanes.caffeine.cache.simulator.generator.IntegerGenerator; import com.github.benmanes.caffeine.cache.simulator.generator.ScrambledZipfianGenerator; @@ -52,6 +51,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheSpec.ReferenceType; import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; import com.github.benmanes.caffeine.cache.testing.RemovalListeners.RejectingRemovalListener; +import com.github.benmanes.caffeine.testing.Awaits; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; 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 18e45b2c05..00b45f235f 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 @@ -16,7 +16,7 @@ package com.github.benmanes.caffeine.cache; import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications; -import static com.github.benmanes.caffeine.matchers.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; 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 5edd8a39da..b941174f61 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 @@ -16,7 +16,7 @@ package com.github.benmanes.caffeine.cache; import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications; -import static com.github.benmanes.caffeine.matchers.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java index a6c00d9ab5..9f17210da9 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java @@ -25,7 +25,7 @@ import org.hamcrest.TypeSafeDiagnosingMatcher; import com.github.benmanes.caffeine.cache.Async.AsyncWeigher; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; import com.google.common.testing.SerializableTester; /** diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidBoundedLocalCache.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidBoundedLocalCache.java index 5a88fb2a03..91fbce14a1 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidBoundedLocalCache.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidBoundedLocalCache.java @@ -15,7 +15,7 @@ */ package com.github.benmanes.caffeine.cache; -import static com.github.benmanes.caffeine.matchers.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -30,7 +30,7 @@ import org.hamcrest.Factory; import org.hamcrest.TypeSafeDiagnosingMatcher; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidLinkedDeque.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidLinkedDeque.java index c0f53b4123..33cf6bb713 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidLinkedDeque.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidLinkedDeque.java @@ -15,7 +15,7 @@ */ package com.github.benmanes.caffeine.cache; -import static com.github.benmanes.caffeine.matchers.IsEmptyIterable.deeplyEmpty; +import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -29,7 +29,7 @@ import org.hamcrest.Factory; import org.hamcrest.TypeSafeDiagnosingMatcher; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; import com.google.common.collect.Sets; /** diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidUnboundedLocalCache.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidUnboundedLocalCache.java index d7234ebdd7..e4d31d5ab5 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidUnboundedLocalCache.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsValidUnboundedLocalCache.java @@ -15,7 +15,7 @@ */ package com.github.benmanes.caffeine.cache; -import static com.github.benmanes.caffeine.matchers.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; @@ -27,7 +27,7 @@ import org.hamcrest.Factory; import org.hamcrest.TypeSafeDiagnosingMatcher; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; /** * A matcher that evaluates a {@link UnboundedLocalCache} to determine if it is in a valid state. diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java index 579b42af07..db85200e89 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java @@ -15,7 +15,7 @@ */ package com.github.benmanes.caffeine.cache; -import static com.github.benmanes.caffeine.matchers.IsEmptyIterable.deeplyEmpty; +import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; import static com.google.common.collect.Iterators.elementsEqual; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MultiThreadedTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MultiThreadedTest.java index 7d5c92419b..4b9e82cc04 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MultiThreadedTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MultiThreadedTest.java @@ -15,38 +15,15 @@ */ package com.github.benmanes.caffeine.cache; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.testng.Assert.fail; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map.Entry; -import java.util.Queue; -import java.util.Random; -import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import org.testng.log4testng.Logger; - -import scala.concurrent.forkjoin.ThreadLocalRandom; -import com.github.benmanes.caffeine.ConcurrentTestHarness; import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; @@ -58,12 +35,13 @@ import com.github.benmanes.caffeine.cache.testing.CacheSpec.ReferenceType; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Stats; import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; +import com.github.benmanes.caffeine.testing.Threads; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.testing.SerializableTester; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import scala.concurrent.forkjoin.ThreadLocalRandom; /** * A test to assert basic concurrency characteristics by validating the internal state after load. @@ -72,21 +50,7 @@ */ @Listeners(CacheValidationListener.class) @Test(groups = "slow", dataProviderClass = CacheProvider.class) -public final class MultiThreadedTest{ - private static final Logger logger = Logger.getLogger(MultiThreadedTest.class); - - private static final int ITERATIONS = 40000; - private static final int NTHREADS = 20; - private static final int TIMEOUT = 30; - - private final List> workingSets; - - public MultiThreadedTest() { - List keys = IntStream.range(0, ITERATIONS).boxed() - .map(i -> ThreadLocalRandom.current().nextInt(ITERATIONS / 100)) - .collect(Collectors.toList()); - workingSets = shuffle(NTHREADS, keys); - } +public final class MultiThreadedTest { @Test(dataProvider = "caches") @CacheSpec(maximumSize = MaximumSize.DISABLED, stats = Stats.DISABLED, @@ -95,7 +59,7 @@ public MultiThreadedTest() { removalListener = Listener.DEFAULT, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void concurrent_unbounded(LoadingCache cache, CacheContext context) { - runTest(cache); + Threads.runTest(cache, operations); } @Test(dataProvider = "caches") @@ -105,7 +69,7 @@ public void concurrent_unbounded(LoadingCache cache, CacheCont removalListener = Listener.DEFAULT, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void concurrent_bounded(LoadingCache cache, CacheContext context) { - runTest(cache); + Threads.runTest(cache, operations); } @Test(dataProvider = "caches") @@ -114,9 +78,9 @@ public void concurrent_bounded(LoadingCache cache, CacheContex expireAfterWrite = Expire.DISABLED, refreshAfterWrite = Expire.DISABLED, removalListener = Listener.DEFAULT, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void async_concurrent_unbounded(AsyncLoadingCache cache, - CacheContext context) { - runTest(cache); + public void async_concurrent_unbounded( + AsyncLoadingCache cache, CacheContext context) { + Threads.runTest(cache, asyncOperations); } @Test(dataProvider = "caches") @@ -125,213 +89,75 @@ public void async_concurrent_unbounded(AsyncLoadingCache cache expireAfterWrite = Expire.FOREVER, refreshAfterWrite = Expire.DISABLED, removalListener = Listener.DEFAULT, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void async_concurrent_bounded(AsyncLoadingCache cache, - CacheContext context) { - runTest(cache); - } - - private void runTest(LoadingCache cache) { - Queue failures = new ConcurrentLinkedQueue<>(); - Runnable thrasher = new Thrasher(cache, workingSets, failures); - executeWithTimeOut(failures, () -> ConcurrentTestHarness.timeTasks(NTHREADS, thrasher)); - assertThat(failures.isEmpty(), is(true)); - } - - private void runTest(AsyncLoadingCache cache) { - Queue failures = new ConcurrentLinkedQueue<>(); - Runnable thrasher = new AsyncThrasher(cache, workingSets, failures); - executeWithTimeOut(failures, () -> ConcurrentTestHarness.timeTasks(NTHREADS, thrasher)); - assertThat(failures.isEmpty(), is(true)); - } - - /** - * Based on the passed in working set, creates N shuffled variants. - * - * @param samples the number of variants to create - * @param baseline the base working set to build from - */ - private static List> shuffle(int samples, Collection baseline) { - List> workingSets = new ArrayList<>(samples); - for (int i = 0; i < samples; i++) { - List workingSet = new ArrayList<>(baseline); - Collections.shuffle(workingSet); - workingSets.add(ImmutableList.copyOf(workingSet)); - } - return ImmutableList.copyOf(workingSets); - } - - /** Executes operations against the cache to simulate random load. */ - private final class Thrasher implements Runnable { - private final LoadingCache cache; - private final List> sets; - private final Queue failures; - private final AtomicInteger index; - - public Thrasher(LoadingCache cache, - List> sets, Queue failures) { - this.index = new AtomicInteger(); - this.failures = failures; - this.cache = cache; - this.sets = sets; - } - - @Override - public void run() { - Random random = new Random(); - int id = index.getAndIncrement(); - for (Integer key : sets.get(id)) { - CacheOperation operation = operations[random.nextInt(operations.length)]; - try { - operation.execute(cache, key, key); - } catch (Throwable t) { - failures.add(String.format("Failed: key %s on operation %s", key, operation)); - throw t; - } - } - } - } - - /** The public operations that can be performed on the cache. */ - interface CacheOperation { - void execute(LoadingCache cache, Integer key, Integer value); + public void async_concurrent_bounded( + AsyncLoadingCache cache, CacheContext context) { + Threads.runTest(cache, asyncOperations); } @SuppressWarnings({"unchecked", "rawtypes"}) - CacheOperation[] operations = new CacheOperation[] { + List, Integer>> operations = ImmutableList.of( // LoadingCache - (cache, key, value) -> cache.get(key), - (cache, key, value) -> cache.getAll(ImmutableList.of(key)), - (cache, key, value) -> cache.refresh(key), + (cache, key) -> cache.get(key), + (cache, key) -> cache.getAll(ImmutableList.of(key)), + (cache, key) -> cache.refresh(key), // Cache - (cache, key, value) -> cache.getIfPresent(key), - (cache, key, value) -> cache.get(key, Function.identity()), - (cache, key, value) -> cache.getAllPresent(ImmutableList.of(key)), - (cache, key, value) -> cache.put(key, value), - (cache, key, value) -> cache.putAll(ImmutableMap.of(key, value)), - (cache, key, value) -> cache.invalidate(key), - (cache, key, value) -> cache.invalidateAll(ImmutableList.of(key)), - (cache, key, value) -> { // expensive so do it less frequently + (cache, key) -> cache.getIfPresent(key), + (cache, key) -> cache.get(key, Function.identity()), + (cache, key) -> cache.getAllPresent(ImmutableList.of(key)), + (cache, key) -> cache.put(key, key), + (cache, key) -> cache.putAll(ImmutableMap.of(key, key)), + (cache, key) -> cache.invalidate(key), + (cache, key) -> cache.invalidateAll(ImmutableList.of(key)), + (cache, key) -> { // expensive so do it less frequently int random = ThreadLocalRandom.current().nextInt(); if ((random & 255) == 0) { cache.invalidateAll(); } }, - (cache, key, value) -> Preconditions.checkState(cache.estimatedSize() >= 0), - (cache, key, value) -> cache.stats(), - (cache, key, value) -> cache.cleanUp(), + (cache, key) -> Preconditions.checkState(cache.estimatedSize() >= 0), + (cache, key) -> cache.stats(), + (cache, key) -> cache.cleanUp(), // Map - (cache, key, value) -> cache.asMap().containsKey(key), - (cache, key, value) -> cache.asMap().containsValue(value), - (cache, key, value) -> cache.asMap().isEmpty(), - (cache, key, value) -> Preconditions.checkState(cache.asMap().size() >= 0), - (cache, key, value) -> cache.asMap().get(key), - (cache, key, value) -> cache.asMap().put(key, value), - (cache, key, value) -> cache.asMap().putAll(ImmutableMap.of(key, value)), - (cache, key, value) -> cache.asMap().putIfAbsent(key, value), - (cache, key, value) -> cache.asMap().remove(key), - (cache, key, value) -> cache.asMap().remove(key, value), - (cache, key, value) -> cache.asMap().replace(key, value), - (cache, key, value) -> cache.asMap().replace(key, value, value), - (cache, key, value) -> { // expensive so do it less frequently + (cache, key) -> cache.asMap().containsKey(key), + (cache, key) -> cache.asMap().containsValue(key), + (cache, key) -> cache.asMap().isEmpty(), + (cache, key) -> Preconditions.checkState(cache.asMap().size() >= 0), + (cache, key) -> cache.asMap().get(key), + (cache, key) -> cache.asMap().put(key, key), + (cache, key) -> cache.asMap().putAll(ImmutableMap.of(key, key)), + (cache, key) -> cache.asMap().putIfAbsent(key, key), + (cache, key) -> cache.asMap().remove(key), + (cache, key) -> cache.asMap().remove(key, key), + (cache, key) -> cache.asMap().replace(key, key), + (cache, key) -> cache.asMap().replace(key, key, key), + (cache, key) -> { // expensive so do it less frequently int random = ThreadLocalRandom.current().nextInt(); if ((random & 255) == 0) { cache.asMap().clear(); } }, - (cache, key, value) -> cache.asMap().keySet().toArray(new Object[cache.asMap().size()]), - (cache, key, value) -> cache.asMap().values().toArray(new Object[cache.asMap().size()]), - (cache, key, value) -> cache.asMap().entrySet().toArray(new Entry[cache.asMap().size()]), - (cache, key, value) -> cache.hashCode(), - (cache, key, value) -> cache.equals(cache), - (cache, key, value) -> cache.toString(), - (cache, key, value) -> { // expensive so do it less frequently + (cache, key) -> cache.asMap().keySet().toArray(new Object[cache.asMap().size()]), + (cache, key) -> cache.asMap().values().toArray(new Object[cache.asMap().size()]), + (cache, key) -> cache.asMap().entrySet().toArray(new Entry[cache.asMap().size()]), + (cache, key) -> cache.hashCode(), + (cache, key) -> cache.equals(cache), + (cache, key) -> cache.toString(), + (cache, key) -> { // expensive so do it less frequently int random = ThreadLocalRandom.current().nextInt(); if ((random & 255) == 0) { SerializableTester.reserialize(cache); } - }, - }; - - /** Executes operations against the async cache to simulate random load. */ - private final class AsyncThrasher implements Runnable { - private final AsyncLoadingCache cache; - private final List> sets; - private final Queue failures; - private final AtomicInteger index; + }); - public AsyncThrasher(AsyncLoadingCache cache, - List> sets, Queue failures) { - this.index = new AtomicInteger(); - this.failures = failures; - this.cache = cache; - this.sets = sets; - } - - @Override - public void run() { - Random random = new Random(); - int id = index.getAndIncrement(); - for (Integer key : sets.get(id)) { - AsyncCacheOperation operation = - asyncOperations[random.nextInt(asyncOperations.length)]; - try { - operation.execute(cache, key, key); - } catch (Throwable t) { - failures.add(String.format("Failed: key %s on operation %s", key, operation)); - throw t; - } - } - } - } - - /** The public operations that can be performed on the async cache. */ - interface AsyncCacheOperation { - void execute(AsyncLoadingCache cache, Integer key, Integer value); - } @SuppressWarnings({"unchecked", "rawtypes"}) - AsyncCacheOperation[] asyncOperations = new AsyncCacheOperation[] { - (cache, key, value) -> cache.getIfPresent(key), - (cache, key, value) -> cache.get(key, k -> value), - (cache, key, value) -> cache.get(key, (k, e) -> CompletableFuture.completedFuture(value)), - (cache, key, value) -> cache.get(key), - (cache, key, value) -> cache.getAll(ImmutableList.of(key)), - (cache, key, value) -> cache.put(key, CompletableFuture.completedFuture(value)), - }; - - /* ---------------- Utilities -------------- */ - - private void executeWithTimeOut(Queue failures, Callable task) { - ExecutorService es = Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder().setDaemon(true).build()); - Future future = es.submit(task); - try { - long timeNS = future.get(TIMEOUT, TimeUnit.SECONDS); - logger.debug("\nExecuted in " + TimeUnit.NANOSECONDS.toSeconds(timeNS) + " second(s)"); - } catch (ExecutionException e) { - fail("Exception during test: " + e.toString(), e); - } catch (TimeoutException e) { - handleTimout(failures, es, e); - } catch (InterruptedException e) { - fail("", e); - } - } - - private void handleTimout(Queue failures, ExecutorService es, TimeoutException e) { - for (StackTraceElement[] trace : Thread.getAllStackTraces().values()) { - for (StackTraceElement element : trace) { - logger.info("\tat " + element); - } - if (trace.length > 0) { - logger.info("------"); - } - } - MoreExecutors.shutdownAndAwaitTermination(es, 10, TimeUnit.SECONDS); - for (String failure : failures) { - logger.debug(failure); - } - fail("Spun forever", e); - } + List, Integer>> asyncOperations = ImmutableList.of( + (cache, key) -> cache.getIfPresent(key), + (cache, key) -> cache.get(key, k -> key), + (cache, key) -> cache.get(key, (k, e) -> CompletableFuture.completedFuture(key)), + (cache, key) -> cache.get(key), + (cache, key) -> cache.getAll(ImmutableList.of(key)), + (cache, key) -> cache.put(key, CompletableFuture.completedFuture(key))); } 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 987b225891..de64e4406b 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 @@ -16,7 +16,7 @@ package com.github.benmanes.caffeine.cache; import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications; -import static com.github.benmanes.caffeine.matchers.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasRemovalNotifications.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasRemovalNotifications.java index 8fde09be65..b22e512017 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasRemovalNotifications.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasRemovalNotifications.java @@ -36,7 +36,7 @@ import com.github.benmanes.caffeine.cache.RemovalNotification; import com.github.benmanes.caffeine.cache.testing.CacheSpec.ReferenceType; import com.github.benmanes.caffeine.cache.testing.RemovalListeners.ConsumingRemovalListener; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; /** * A matcher that evaluates the {@link ConsumingRemovalListener} recorded all of the removal diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasStats.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasStats.java index b497a4477c..6ed3a8dd71 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasStats.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/HasStats.java @@ -25,7 +25,7 @@ import org.hamcrest.TypeSafeDiagnosingMatcher; import com.github.benmanes.caffeine.cache.stats.CacheStats; -import com.github.benmanes.caffeine.matchers.DescriptionBuilder; +import com.github.benmanes.caffeine.testing.DescriptionBuilder; /** * A matcher that evaluates if the {@link CacheStats} recorded all of the statistical events. diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/locks/NonReentrantLockTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/locks/NonReentrantLockTest.java index 928d97b049..4eeb8e68c6 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/locks/NonReentrantLockTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/locks/NonReentrantLockTest.java @@ -28,8 +28,8 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import com.github.benmanes.caffeine.Awaits; import com.github.benmanes.caffeine.ConcurrentTestHarness; +import com.github.benmanes.caffeine.testing.Awaits; import com.google.common.testing.SerializableTester; /** diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/Awaits.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Awaits.java similarity index 96% rename from caffeine/src/test/java/com/github/benmanes/caffeine/Awaits.java rename to caffeine/src/test/java/com/github/benmanes/caffeine/testing/Awaits.java index 9bfc85a19e..42c48f796d 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/Awaits.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Awaits.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.benmanes.caffeine; +package com.github.benmanes.caffeine.testing; import java.util.concurrent.TimeUnit; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/DescriptionBuilder.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/DescriptionBuilder.java similarity index 98% rename from caffeine/src/test/java/com/github/benmanes/caffeine/matchers/DescriptionBuilder.java rename to caffeine/src/test/java/com/github/benmanes/caffeine/testing/DescriptionBuilder.java index 54251fad08..44c82bfe22 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/DescriptionBuilder.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/DescriptionBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.benmanes.caffeine.matchers; +package com.github.benmanes.caffeine.testing; import java.util.function.Supplier; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsEmptyIterable.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsEmptyIterable.java similarity index 99% rename from caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsEmptyIterable.java rename to caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsEmptyIterable.java index a45eae8051..bbcbd8d57d 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsEmptyIterable.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsEmptyIterable.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.benmanes.caffeine.matchers; +package com.github.benmanes.caffeine.testing; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsEmptyMap.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsEmptyMap.java similarity index 94% rename from caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsEmptyMap.java rename to caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsEmptyMap.java index 4a53c5d785..c79b7d4e1c 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsEmptyMap.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsEmptyMap.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.benmanes.caffeine.matchers; +package com.github.benmanes.caffeine.testing; -import static com.github.benmanes.caffeine.matchers.IsEmptyIterable.deeplyEmpty; +import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.is; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsFutureValue.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsFutureValue.java similarity index 97% rename from caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsFutureValue.java rename to caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsFutureValue.java index a28d0fcce3..2ce7a25fd7 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/matchers/IsFutureValue.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IsFutureValue.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.benmanes.caffeine.matchers; +package com.github.benmanes.caffeine.testing; import java.util.concurrent.Future; diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Threads.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Threads.java new file mode 100644 index 0000000000..100843ca88 --- /dev/null +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Threads.java @@ -0,0 +1,157 @@ +/* + * 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.testing; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.testng.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.testng.log4testng.Logger; + +import com.github.benmanes.caffeine.ConcurrentTestHarness; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +/** + * Shared utilities for multi-threaded tests. + * + * @author ben.manes@gmail.com (Ben Manes) + */ +public final class Threads { + private static final Logger logger = Logger.getLogger(Threads.class); + + public static final int ITERATIONS = 40000; + public static final int NTHREADS = 20; + public static final int TIMEOUT = 30; + + private Threads() {} + + public static void runTest(A collection, List> operations) { + Queue failures = new ConcurrentLinkedQueue<>(); + Runnable thrasher = new Thrasher(collection, failures, operations); + Threads.executeWithTimeOut(failures, () -> + ConcurrentTestHarness.timeTasks(Threads.NTHREADS, thrasher)); + assertThat(failures.isEmpty(), is(true)); + } + + public static void executeWithTimeOut(Queue failures, Callable task) { + ExecutorService es = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setDaemon(true).build()); + Future future = es.submit(task); + try { + long timeNS = future.get(TIMEOUT, TimeUnit.SECONDS); + logger.debug("\nExecuted in " + TimeUnit.NANOSECONDS.toSeconds(timeNS) + " second(s)"); + } catch (ExecutionException e) { + fail("Exception during test: " + e.toString(), e); + } catch (TimeoutException e) { + handleTimout(failures, es, e); + } catch (InterruptedException e) { + fail("", e); + } + } + + public static void handleTimout(Queue failures, ExecutorService es, TimeoutException e) { + for (StackTraceElement[] trace : Thread.getAllStackTraces().values()) { + for (StackTraceElement element : trace) { + logger.info("\tat " + element); + } + if (trace.length > 0) { + logger.info("------"); + } + } + MoreExecutors.shutdownAndAwaitTermination(es, 10, TimeUnit.SECONDS); + for (String failure : failures) { + logger.debug(failure); + } + fail("Spun forever", e); + } + + public static List> workingSets(int nThreads, int iterations) { + List keys = IntStream.range(0, iterations).boxed() + .map(i -> ThreadLocalRandom.current().nextInt(iterations / 100)) + .collect(Collectors.toList()); + return shuffle(nThreads, keys); + } + + /** + * Based on the passed in working set, creates N shuffled variants. + * + * @param samples the number of variants to create + * @param baseline the base working set to build from + */ + private static List> shuffle(int samples, Collection baseline) { + List> workingSets = new ArrayList<>(samples); + for (int i = 0; i < samples; i++) { + List workingSet = new ArrayList<>(baseline); + Collections.shuffle(workingSet); + workingSets.add(ImmutableList.copyOf(workingSet)); + } + return ImmutableList.copyOf(workingSets); + } + + /** Executes operations against the cache to simulate random load. */ + public static final class Thrasher implements Runnable { + private final List> operations; + private final List> sets; + private final Queue failures; + private final AtomicInteger index; + private final A collection; + + public Thrasher(A collection, Queue failures, List> operations) { + this.sets = workingSets(Threads.NTHREADS, Threads.ITERATIONS); + this.index = new AtomicInteger(); + this.operations = operations; + this.collection = collection; + this.failures = failures; + } + + @Override + public void run() { + Random random = new Random(); + int id = index.getAndIncrement(); + for (Integer e : sets.get(id)) { + BiConsumer operation = operations.get(random.nextInt(operations.size())); + try { + operation.accept(collection, e); + } catch (Throwable t) { + failures.add(String.format("Failed: key %s on operation %s", e, operation)); + throw t; + } + } + } + } +} diff --git a/guava/src/test/java/com/github/benmanes/caffeine/PackageSanityTests.java b/guava/src/test/java/com/github/benmanes/caffeine/PackageSanityTests.java index 4c6c0c0ce9..7ff1513287 100644 --- a/guava/src/test/java/com/github/benmanes/caffeine/PackageSanityTests.java +++ b/guava/src/test/java/com/github/benmanes/caffeine/PackageSanityTests.java @@ -15,6 +15,7 @@ */ package com.github.benmanes.caffeine; +import com.github.benmanes.caffeine.testing.Awaits; import com.google.common.testing.AbstractPackageSanityTests; /**