Author: desruisseaux
Date: Thu Oct 11 05:41:10 2012
New Revision: 1396913
URL: http://svn.apache.org/viewvc?rev=1396913&view=rev
Log:
Added tests for Cache.
Added:
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java
(with props)
Modified:
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakHashSetTest.java
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakValueHashMapTest.java
Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java?rev=1396913&r1=1396912&r2=1396913&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java (original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java Thu
Oct 11 05:41:10 2012
@@ -20,6 +20,7 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.Callable;
+import java.lang.reflect.UndeclaredThrowableException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -39,6 +40,14 @@ import static org.junit.Assert.*;
*/
public final strictfp class TestUtilities extends Static {
/**
+ * Maximal time that {@code waitFoo()} methods can wait, in milliseconds.
+ *
+ * @see #waitForBlockedState(Thread)
+ * @see #waitForGarbageCollection(Callable)
+ */
+ private static final int MAXIMAL_WAIT_TIME = 1000;
+
+ /**
* Date parser and formatter using the {@code "yyyy-MM-dd HH:mm:ss"} pattern
* and UTC time zone.
*/
@@ -87,8 +96,54 @@ public final strictfp class TestUtilitie
}
/**
+ * If the given failure is not null, re-thrown it as an {@link Error} or
+ * {@link RuntimeException}. Otherwise do nothing.
+ *
+ * @param failure The exception to re-thrown if non-null.
+ */
+ public static void rethrownIfNotNull(final Throwable failure) {
+ if (failure != null) {
+ if (failure instanceof Error) {
+ throw (Error) failure;
+ }
+ if (failure instanceof RuntimeException) {
+ throw (RuntimeException) failure;
+ }
+ throw new UndeclaredThrowableException(failure);
+ }
+ }
+
+ /**
+ * Waits up to one second for the given thread to reach the
+ * {@linkplain java.lang.Thread.State#BLOCKED blocked} or the
+ * {@linkplain java.lang.Thread.State#WAITING waiting} state.
+ *
+ * @param thread The thread to wait for blocked or waiting state.
+ * @throws IllegalThreadStateException If the thread has terminated its execution,
+ * or has not reached the waiting or blocked state before the timeout.
+ * @throws InterruptedException If this thread has been interrupted while waiting.
+ */
+ public static void waitForBlockedState(final Thread thread) throws IllegalThreadStateException,
InterruptedException {
+ int retry = MAXIMAL_WAIT_TIME / 5; // 5 shall be the same number than in the call
to Thread.sleep.
+ do {
+ Thread.sleep(5);
+ switch (thread.getState()) {
+ case WAITING:
+ case BLOCKED: return;
+ case TERMINATED: throw new IllegalThreadStateException("The thread has completed
execution.");
+ }
+ } while (--retry != 0);
+ throw new IllegalThreadStateException("The thread is not in a blocked or waiting
state.");
+ }
+
+ /**
* Waits up to one second for the garbage collector to do its work. This method can be
invoked
* only if {@link TestConfiguration#allowGarbageCollectorDependentTests()} returns {@code
true}.
+ * <p>
+ * Note that this method does not throw any exception if the given condition has not
been
+ * reached before the timeout. Instead, it is the caller responsibility to test the return
+ * value. This method is designed that way because the caller can usually produce a more
+ * accurate error message about which value has not been garbage collected as expected.
*
* @param stopCondition A condition which return {@code true} if this method can stop
waiting,
* or {@code false} if it needs to ask again for garbage collection.
@@ -98,7 +153,7 @@ public final strictfp class TestUtilitie
*/
public static boolean waitForGarbageCollection(final Callable<Boolean> stopCondition)
throws InterruptedException {
assertTrue("GC-dependent tests not allowed in this run.", TestConfiguration.allowGarbageCollectorDependentTests());
- int retry = 20;
+ int retry = MAXIMAL_WAIT_TIME / 50; // 50 shall be the same number than in the call
to Thread.sleep.
boolean stop;
do {
if (--retry == 0) {
Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1396913&r1=1396912&r2=1396913&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
Thu Oct 11 05:41:10 2012
@@ -44,10 +44,11 @@ import org.junit.runners.Suite;
org.apache.sis.util.type.TypesTest.class,
org.apache.sis.util.type.SimpleInternationalStringTest.class,
org.apache.sis.util.type.DefaultInternationalStringTest.class,
+ org.apache.sis.math.MathFunctionsTest.class,
org.apache.sis.internal.util.ReferenceQueueConsumerTest.class,
org.apache.sis.util.collection.WeakHashSetTest.class,
org.apache.sis.util.collection.WeakValueHashMapTest.class,
- org.apache.sis.math.MathFunctionsTest.class
+ org.apache.sis.util.collection.CacheTest.class
})
public final strictfp class UtilityTestSuite extends TestSuite {
}
Added: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java?rev=1396913&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java
(added)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java
Thu Oct 11 05:41:10 2012
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.util.collection;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.test.TestUtilities;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.DependsOnMethod;
+import org.junit.Test;
+
+import static java.lang.StrictMath.*;
+import static java.util.Collections.singleton;
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests the {@link Cache} with simple tests and a {@linkplain #stress() stress} test.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.3 (derived from geotk-2.0)
+ * @version 0.3
+ * @module
+ */
+@DependsOn(WeakValueHashMapTest.class)
+public final strictfp class CacheTest extends TestCase {
+ /**
+ * Tests {@link Cache} as a {@link java.util.Map} using strong references.
+ * The tested {@code Cache} shall behave like a standard {@link HashMap},
+ * except for element order.
+ *
+ * @see WeakValueHashMapTest#testStrongReferences()
+ */
+ @Test
+ public void testStrongReferences() {
+ WeakValueHashMapTest.testStrongReferences(new Cache<Integer,Integer>(500, 0,
false));
+ }
+
+ /**
+ * Tests {@link Cache} as a {@link java.util.Map} using weak references. In this test,
we
+ * have to keep in mind than some elements in {@code weakMap} may disappear at any time.
+ *
+ * @throws InterruptedException If the test has been interrupted.
+ *
+ * @see WeakValueHashMapTest#testWeakReferences()
+ */
+ @Test
+ @DependsOnMethod("testStrongReferences")
+ public void testWeakReferences() throws InterruptedException {
+ WeakValueHashMapTest.testWeakReferences(new Cache<Integer,Integer>(500, 0,
false));
+ }
+
+ /**
+ * Tests adding a single value using the {@link Cache.Handler#putAndUnlock(Object)} method.
+ * This method does all the operations in a single thread.
+ */
+ @Test
+ public void testPutAndUnlock() {
+ final String key = "The key";
+ final String value = "The value";
+ final Cache<String,String> cache = new Cache<>();
+ assertTrue("No initial value expected.", cache.isEmpty());
+ assertNull("No initial value expected.", cache.peek(key));
+
+ final Cache.Handler<String> handler = cache.lock(key);
+ assertNull("No initial value expected.", handler.peek());
+ handler.putAndUnlock(value);
+
+ assertEquals(1, cache.size());
+ assertEquals(value, cache.peek(key));
+ assertEquals(singleton(key), cache.keySet());
+ assertEquals(singleton(new SimpleEntry<>(key, value)), cache.entrySet());
+ }
+
+ /**
+ * Tests the cache when a thread is blocking a second one.
+ * The second thread tries to write a value while the first thread holds the lock.
+ *
+ * @throws InterruptedException If the test has been interrupted.
+ */
+ @Test
+ @DependsOnMethod("testPutAndUnlock")
+ public void testThreadBlocking() throws InterruptedException {
+ final String keyByMainThread = "keyByMainThread";
+ final String valueByMainThread = "valueByMainThread";
+ final String keyByOtherThread = "keyByOtherThread";
+ final String valueByOtherThread = "valueByOtherThread";
+ final Cache<String,String> cache = new Cache<>();
+ final class OtherThread extends Thread {
+ /**
+ * If an error occurred, the cause. It may be an {@link AssertionError}.
+ */
+ Throwable failure;
+
+ /**
+ * Reads the value added by the main thread, then adds an other value.
+ * The first operation shall block while the main thread holds the lock.
+ */
+ @Override public void run() {
+ try {
+ final Cache.Handler<String> handler = cache.lock(keyByMainThread);
+ assertTrue(handler instanceof Cache<?,?>.Work.Wait);
+ assertSame(valueByMainThread, handler.peek());
+ handler.putAndUnlock(valueByMainThread);
+ assertSame(valueByMainThread, cache.peek(keyByMainThread));
+ } catch (Throwable e) {
+ failure = e;
+ }
+ try {
+ final Cache.Handler<String> handler = cache.lock(keyByOtherThread);
+ assertTrue(handler instanceof Cache<?,?>.Work);
+ assertNull(handler.peek());
+ handler.putAndUnlock(valueByOtherThread);
+ assertSame(valueByOtherThread, cache.peek(keyByOtherThread));
+ } catch (Throwable e) {
+ if (failure == null) {
+ failure = e;
+ } else {
+ failure.addSuppressed(e);
+ }
+ }
+ }
+ }
+ /*
+ * Gets the lock, then start the second thread which will try to write
+ * a value for the same key. The second thread shall block.
+ */
+ final Cache.Handler<String> handler = cache.lock(keyByMainThread);
+ assertTrue(handler instanceof Cache<?,?>.Work);
+ final OtherThread thread = new OtherThread();
+ thread.start();
+ TestUtilities.waitForBlockedState(thread);
+ assertNull("The blocked thread shall not have added a value.", cache.peek(keyByOtherThread));
+ /*
+ * Write. This will release the lock and let the other thread continue its job.
+ */
+ handler.putAndUnlock(valueByMainThread);
+ thread.join();
+ TestUtilities.rethrownIfNotNull(thread.failure);
+ /*
+ * Checks the map content.
+ */
+ final Map<String,String> expected = new HashMap<>(4);
+ assertNull(expected.put( keyByMainThread, valueByMainThread));
+ assertNull(expected.put(keyByOtherThread, valueByOtherThread));
+ assertMapEquals(expected, cache);
+ }
+
+ /**
+ * Validates the entries created by the {@link #stress()} test. The check performed in
+ * this method shall obviously be consistent with the values created by {@code stress()}.
+ */
+ private static void validateStressEntries(final Map<Integer,Integer> cache) {
+ for (final Map.Entry<Integer,Integer> entry : cache.entrySet()) {
+ final int key = entry.getKey();
+ final int value = entry.getValue();
+ assertEquals(key*key, value);
+ }
+ }
+
+ /**
+ * Starts many threads writing in the same cache, with a high probability that two threads
+ * ask for the same key in some occasions.
+ *
+ * @throws InterruptedException If the test has been interrupted.
+ */
+ @Test
+ @DependsOnMethod("testThreadBlocking")
+ public void stress() throws InterruptedException {
+ final int count = 10000;
+ final Cache<Integer,Integer> cache = new Cache<>();
+ final AtomicReference<Throwable> failures = new AtomicReference<>();
+ final class WriterThread extends Thread {
+ /**
+ * Incremented every time a value has been added. This is not the number of time
the
+ * loop has been executed, since this variable is not incremented when a value
already
+ * exists for a key.
+ */
+ int addCount;
+
+ /**
+ * Put random values in the map.
+ */
+ @Override public void run() {
+ for (int i=0; i<count; i++) {
+ final Integer key = i;
+ final Integer expected = new Integer(i * i); // We really want new instance.
+ final Integer value;
+ try {
+ value = cache.getOrCreate(key, new Callable<Integer>() {
+ @Override public Integer call() {
+ return expected;
+ }
+ });
+ assertEquals(expected, value);
+ } catch (Throwable e) {
+ if (!failures.compareAndSet(null, e)) {
+ failures.get().addSuppressed(e);
+ }
+ continue;
+ }
+ if (expected == value) { // Identity comparison (not value comparison).
+ addCount++;
+ yield(); // Gives a chance to other threads.
+ }
+ }
+ }
+ }
+ final WriterThread[] threads = new WriterThread[50];
+ for (int i=0; i<threads.length; i++) threads[i] = new WriterThread();
+ for (int i=0; i<threads.length; i++) threads[i].start();
+ for (int i=0; i<threads.length; i++) threads[i].join();
+ TestUtilities.rethrownIfNotNull(failures.get());
+ /*
+ * Verifies the values.
+ */
+ validateStressEntries(cache);
+ assertTrue("Should not have more entries than what we put in.", cache.size() <=
count);
+ assertFalse("Some entries should be retained by strong references.", cache.isEmpty());
+ if (out != null) {
+ out.println();
+ out.println("The numbers below are for tuning the test only. The output is somewhat");
+ out.println("random so we can not check it in a test suite. However if the test
is");
+ out.println("properly tuned, most values should be non-zero.");
+ out.println();
+ out.println("Number of times a cached value has been reused, for each thread:");
+ for (int i=0; i<threads.length;) {
+ final String n = String.valueOf(threads[i++].addCount);
+ out.print(CharSequences.spaces(6 - n.length()));
+ out.print(n);
+ if ((i % 10) == 0) {
+ out.println();
+ }
+ }
+ out.println();
+ out.println("Now observe how the background thread cleans the cache.");
+ long time = System.nanoTime();
+ for (int i=0; i<10; i++) {
+ final long t = System.nanoTime();
+ out.printf("Cache size: %4d (after %3d ms)%n", cache.size(), round((t - time)
/ 1E+6));
+ time = t;
+ Thread.sleep(250);
+ if (i >= 2) {
+ System.gc();
+ }
+ }
+ out.println();
+ out.flush();
+ }
+ System.gc();
+ validateStressEntries(cache);
+ }
+}
Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakHashSetTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakHashSetTest.java?rev=1396913&r1=1396912&r2=1396913&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakHashSetTest.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakHashSetTest.java
Thu Oct 11 05:41:10 2012
@@ -50,7 +50,8 @@ public final strictfp class WeakHashSetT
/**
* Tests the {@link WeakHashSet} using strong references.
- * The tested {@link WeakHashSet} should behave like a standard {@link Set} object.
+ * The tested {@code WeakHashSet} shall behave like a standard {@link HashSet},
+ * except for element order.
*/
@Test
public void testStrongReferences() {
Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakValueHashMapTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakValueHashMapTest.java?rev=1396913&r1=1396912&r2=1396913&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakValueHashMapTest.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/util/collection/WeakValueHashMapTest.java
Thu Oct 11 05:41:10 2012
@@ -16,6 +16,7 @@
*/
package org.apache.sis.util.collection;
+import java.util.Map;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.Callable;
@@ -50,13 +51,24 @@ public final strictfp class WeakValueHas
/**
* Tests the {@link WeakValueHashMap} using strong references.
- * The tested {@link WeakValueHashMap} should behave like a standard {@link Map} object.
+ * The tested {@code WeakValueHashMap} shall behave like a standard {@link HashMap},
+ * except for element order.
*/
@Test
public void testStrongReferences() {
+ testStrongReferences(new WeakValueHashMap<Integer,Integer>(Integer.class));
+ }
+
+ /**
+ * Implementation of the {@link #testStrongReferences()} method,
+ * to be reused by {@link CacheTest}.
+ *
+ * @param weakMap The map implementation to test.
+ */
+ static void testStrongReferences(final Map<Integer,Integer> weakMap) {
final Random random = new Random();
for (int pass=0; pass<NUM_RETRY; pass++) {
- final WeakValueHashMap<Integer,Integer> weakMap = new WeakValueHashMap<>(Integer.class);
+ weakMap.clear();
final HashMap<Integer,Integer> strongMap = new HashMap<>();
for (int i=0; i<SAMPLE_SIZE; i++) {
final Integer key = random.nextInt(SAMPLE_SIZE);
@@ -86,9 +98,19 @@ public final strictfp class WeakValueHas
@Test
@DependsOnMethod("testStrongReferences")
public void testWeakReferences() throws InterruptedException {
+ testWeakReferences(new WeakValueHashMap<Integer,Integer>(Integer.class));
+ }
+
+ /**
+ * Implementation of the {@link #testWeakReferences()} method,
+ * to be reused by {@link CacheTest}.
+ *
+ * @param weakMap The map implementation to test.
+ */
+ static void testWeakReferences(final Map<Integer,Integer> weakMap) throws InterruptedException
{
final Random random = new Random();
for (int pass=0; pass<NUM_RETRY; pass++) {
- final WeakValueHashMap<Integer,Integer> weakMap = new WeakValueHashMap<>(Integer.class);
+ weakMap.clear();
final HashMap<Integer,Integer> strongMap = new HashMap<>();
for (int i=0; i<SAMPLE_SIZE; i++) {
// We really want new instances here.
|