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;
/**