sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1396913 - in /sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis: test/TestUtilities.java test/suite/UtilityTestSuite.java util/collection/CacheTest.java util/collection/WeakHashSetTest.java util/collection/WeakValueHashMapTest.java
Date Thu, 11 Oct 2012 05:41:11 GMT
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.



Mime
View raw message