Author: henrib
Date: Wed Jan  6 19:46:18 2010
New Revision: 896643

URL: http://svn.apache.org/viewvc?rev=896643&view=rev
Log:
Refactored to to run multi-threaded; this verifies the expression cache and 
volatile AST node cache usage do not induce errors.

Modified:
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/CacheTest.java

Modified: 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/CacheTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/CacheTest.java?rev=896643&r1=896642&r2=896643&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/CacheTest.java 
(original)
+++ 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/CacheTest.java 
Wed Jan  6 19:46:18 2010
@@ -16,8 +16,13 @@
  */
 package org.apache.commons.jexl2;
 
-import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Verifies cache & tryExecute
@@ -33,7 +38,10 @@
         jexl.setLenient(false);
         jexl.setSilent(false);
     }
-    private static final int LOOPS = 1024;
+
+    // LOOPS & THREADS
+    private static final int LOOPS = 4096;
+    private static final int NTHREADS = 4;
     // A pseudo random mix of accessors
     private static final int[] MIX = {
         0, 0, 3, 3, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 1, 2, 2, 2,
@@ -44,7 +52,12 @@
     protected void tearDown() throws Exception {
         debuggerCheck(jexl);
     }
-    
+
+    /**
+     * A set of classes that define different getter/setter methods for the 
same properties.
+     * The goal is to verify that the cached JexlPropertyGet / JexlPropertySet 
in the AST Nodes are indeed
+     * volatile and do not generate errors even when multiple threads 
concurently hammer them.
+     */
     public static class Cached {
         public String compute(String arg) {
             if (arg == null) {
@@ -237,6 +250,9 @@
         }
     }
 
+    /**
+     * A helper class to pass arguments in tests (instances of getter/setter 
exercising classes).
+     */
     static class TestCacheArguments {
         Cached0 c0 = new Cached0();
         Cached1 c1 = new Cached1();
@@ -246,10 +262,17 @@
         Object[] ca = {
             c0, c1, c2, c3, c4
         };
-        Object value = null;
+        Object[] value = null;
     }
 
-    void doAssign(TestCacheArguments x, int loops, boolean cache) throws 
Exception {
+    /**
+     * Run same test function in NTHREADS in parallel.
+     * @param ctask the task / test
+     * @param loops number of loops to perform
+     * @param cache whether jexl cache is used or not
+     * @throws Exception if anything goes wrong
+     */
+    void runThreaded(Class<? extends Task> ctask, int loops, boolean cache) 
throws Exception {
         if (loops == 0) {
             loops = MIX.length;
         }
@@ -258,251 +281,322 @@
         } else {
             jexl.setCache(0);
         }
-        Map<String, Object> vars = new HashMap<String,Object>();
-        JexlContext jc = new MapContext(vars);
-        Expression cacheGetValue = jexl.createExpression("cache.value");
-        Expression cacheSetValue = jexl.createExpression("cache.value = 
value");
-        Object result;
+        java.util.concurrent.ExecutorService execs = 
java.util.concurrent.Executors.newFixedThreadPool(NTHREADS);
+        List<Task> tasks = new ArrayList<Task>(NTHREADS);
+        for(int t = 0; t < NTHREADS; ++t) {
+            tasks.add(jexl.newInstance(ctask, loops));
+        }
+        // let's not wait for more than a minute
+        List<Future<Integer>> futures = execs.invokeAll(tasks, 60, 
TimeUnit.SECONDS);
+        // check that all returned loops
+        for(Future<Integer> future : futures) {
+            assertEquals(Integer.valueOf(loops), future.get());
+        }
+    }
 
-        for (int l = 0; l < loops; ++l) {
-            int mix = MIX[l % MIX.length];
+    /**
+     * The base class for MT tests.
+     */
+    public abstract static class Task implements Callable<Integer> {
+        final TestCacheArguments args = new TestCacheArguments();
+        final int loops;
+        final Map<String, Object> vars = new HashMap<String, Object>();
+        final JexlContext jc = new MapContext(vars);
+
+        Task(int loops) {
+            this.loops = loops;
+        }
+
+        public abstract Integer call() throws Exception;
+
+        /**
+         * The actual test function; assigns and checks.
+         * <p>The expression will be evaluated against different classes in 
parallel.
+         * This verifies that neither the volatile cache in the AST nor the 
expression cache in the JEXL engine
+         * induce errors.</p>
+         * <p>
+         * Using it as a micro benchmark, it shows creating expression as the 
dominating cost; the expression
+         * cache takes care of this.
+         * By moving the expression creations out of the main loop, it also 
shows that the volatile cache speeds
+         * things up around 2x.
+         * </p>
+         * @param value the argument value to control
+         * @return the number of loops performed
+         */
+        public Integer runAssign(Object value) {
+            args.value = new Object[]{value};
+            Object result;
+
+            Expression cacheGetValue = jexl.createExpression("cache.value");
+            Expression cacheSetValue = jexl.createExpression("cache.value = 
value");
+            for (int l = 0; l < loops; ++l) {
+                int px = (int) Thread.currentThread().getId();
+                int mix = MIX[(l + px) % MIX.length];
+
+                vars.put("cache", args.ca[mix]);
+                vars.put("value", args.value[0]);
+                result = cacheSetValue.evaluate(jc);
+                if (args.value[0] == null) {
+                    assertNull(cacheSetValue.toString(), result);
+                } else {
+                    assertEquals(cacheSetValue.toString(), args.value[0], 
result);
+                }
 
-            vars.put("cache", x.ca[mix]);
-            vars.put("value", x.value);
-            result = cacheSetValue.evaluate(jc);
-            if (x.value == null) {
-                assertNull(cacheSetValue.toString(), result);
-            } else {
-                assertEquals(cacheSetValue.toString(), x.value, result);
-            }
+                result = cacheGetValue.evaluate(jc);
+                if (args.value[0] == null) {
+                    assertEquals(cacheGetValue.toString(), "Cached" + mix + 
":na", result);
+                } else {
+                    assertEquals(cacheGetValue.toString(), "Cached" + mix + 
":" + args.value[0], result);
+                }
 
-            result = cacheGetValue.evaluate(jc);
-            if (x.value == null) {
-                assertEquals(cacheGetValue.toString(), "Cached" + mix + ":na", 
result);
-            } else {
-                assertEquals(cacheGetValue.toString(), "Cached" + mix + ":" + 
x.value, result);
             }
 
+            return Integer.valueOf(loops);
         }
     }
 
-    public void testNullAssignNoCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        doAssign(args, LOOPS, false);
+    /**
+     * A task to check assignment.
+     */
+    public static class AssignTask extends Task {
+        public AssignTask(int loops) {
+            super(loops);
+        }
+        public Integer call() throws Exception {
+            return runAssign("foo");
+        }
     }
 
-    public void testNullAssignCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        doAssign(args, LOOPS, true);
+    /**
+     * A task to check null assignment.
+     */
+    public static class AssignNullTask extends Task {
+        public AssignNullTask(int loops) {
+            super(loops);
+        }
+        public Integer call() throws Exception {
+            return runAssign(null);
+        }
     }
 
-    public void testAssignNoCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.value = "foo";
-        doAssign(args, LOOPS, false);
-    }
+    /**
+     * A task to check boolean assignment.
+     */
+    public static class AssignBooleanTask extends Task {
+        public AssignBooleanTask(int loops) {
+            super(loops);
+        }
+        public Integer call() throws Exception {
+            return runAssignBoolean(Boolean.TRUE);
+        }
 
-    public void testAssignCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.value = "foo";
-        doAssign(args, LOOPS, true);
+        /** The actual test function. */
+        private Integer runAssignBoolean(Boolean value) {
+            args.value = new Object[]{value};
+            Expression cacheGetValue = jexl.createExpression("cache.flag");
+            Expression cacheSetValue = jexl.createExpression("cache.flag = 
value");
+            Object result;
+
+            for (int l = 0; l < loops; ++l) {
+                int px = (int) Thread.currentThread().getId();
+                int mix = MIX[(l + px) % MIX.length];
+
+                vars.put("cache", args.ca[mix]);
+                vars.put("value", args.value[0]);
+                result = cacheSetValue.evaluate(jc);
+                assertEquals(cacheSetValue.toString(), args.value[0], result);
+
+                result = cacheGetValue.evaluate(jc);
+                assertEquals(cacheGetValue.toString(), args.value[0], result);
+
+            }
+
+            return Integer.valueOf(loops);
+        }
     }
 
-    void doAssignBoolean(TestCacheArguments x, int loops, boolean cache) 
throws Exception {
-        if (loops == 0) {
-            loops = MIX.length;
+    /**
+     * A task to check list assignment.
+     */
+    public static class AssignListTask extends Task {
+        public AssignListTask(int loops) {
+            super(loops);
         }
-        if (cache) {
-            jexl.setCache(32);
-        } else {
-            jexl.setCache(0);
+
+        public Integer call() throws Exception {
+            return runAssignList();
         }
-        Map<String, Object> vars = new HashMap<String,Object>();
-        JexlContext jc = new MapContext(vars);
-        Expression cacheGetValue = jexl.createExpression("cache.flag");
-        Expression cacheSetValue = jexl.createExpression("cache.flag = value");
-        Object result;
+        /** The actual test function. */
+        private Integer runAssignList() {
+            args.value = new Object[]{"foo"};
+            java.util.ArrayList<String> c1 = new 
java.util.ArrayList<String>(2);
+            c1.add("foo");
+            c1.add("bar");
+            args.ca = new Object[]{
+                        new String[]{"one", "two"},
+                        c1
+                    };
 
-        for (int l = 0; l < loops; ++l) {
-            int mix = MIX[l % MIX.length];
+            Expression cacheGetValue = jexl.createExpression("cache.0");
+            Expression cacheSetValue = jexl.createExpression("cache[0] = 
value");
+            Object result;
 
-            vars.put("cache", x.ca[mix]);
-            vars.put("value", x.value);
-            result = cacheSetValue.evaluate(jc);
-            assertEquals(cacheSetValue.toString(), x.value, result);
+            for (int l = 0; l < loops; ++l) {
+                int px = (int) Thread.currentThread().getId();
+                int mix = MIX[(l + px) % MIX.length] % args.ca.length;
 
-            result = cacheGetValue.evaluate(jc);
-            assertEquals(cacheGetValue.toString(), x.value, result);
+                vars.put("cache", args.ca[mix]);
+                vars.put("value", args.value[0]);
+                result = cacheSetValue.evaluate(jc);
+                assertEquals(cacheSetValue.toString(), args.value[0], result);
+
+                result = cacheGetValue.evaluate(jc);
+                assertEquals(cacheGetValue.toString(), args.value[0], result);
+            }
 
+            return Integer.valueOf(loops);
         }
     }
 
-    public void testAssignBooleanNoCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.value = Boolean.TRUE;
-        doAssignBoolean(args, LOOPS, false);
-    }
 
-    public void testAssignBooleanCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.value = Boolean.TRUE;
-        doAssignBoolean(args, LOOPS, true);
+    public void testNullAssignNoCache() throws Exception {
+        runThreaded(AssignNullTask.class, LOOPS, false);
     }
 
-    void doAssignList(TestCacheArguments x, int loops, boolean cache) throws 
Exception {
-        if (loops == 0) {
-            loops = MIX.length;
-        }
-        if (cache) {
-            jexl.setCache(32);
-        } else {
-            jexl.setCache(0);
-        }
-        Map<String, Object> vars = new HashMap<String,Object>();
-        JexlContext jc = new MapContext(vars);
-        Expression cacheGetValue = jexl.createExpression("cache.0");
-        Expression cacheSetValue = jexl.createExpression("cache[0] = value");
-        Object result;
+    public void testNullAssignCache() throws Exception {
+        runThreaded(AssignNullTask.class, LOOPS, true);
+    }
 
-        for (int l = 0; l < loops; ++l) {
-            int mix = MIX[l % MIX.length] % x.ca.length;
+    public void testAssignNoCache() throws Exception {
+        runThreaded(AssignTask.class, LOOPS, false);
+    }
 
-            vars.put("cache", x.ca[mix]);
-            vars.put("value", x.value);
-            result = cacheSetValue.evaluate(jc);
-            assertEquals(cacheSetValue.toString(), x.value, result);
+    public void testAssignCache() throws Exception {
+        runThreaded(AssignTask.class, LOOPS, true);
+    }
 
-            result = cacheGetValue.evaluate(jc);
-            assertEquals(cacheGetValue.toString(), x.value, result);
+    public void testAssignBooleanNoCache() throws Exception {
+        runThreaded(AssignBooleanTask.class, LOOPS, false);
+    }
 
-        }
+    public void testAssignBooleanCache() throws Exception {
+        runThreaded(AssignBooleanTask.class, LOOPS, true);
     }
 
     public void testAssignListNoCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.value = "foo";
-        java.util.ArrayList<String> c1 = new java.util.ArrayList<String>(2);
-        c1.add("foo");
-        c1.add("bar");
-        args.ca = new Object[]{
-            new String[]{"one", "two"},
-            c1
-        };
-        doAssignList(args, LOOPS, false);
+        runThreaded(AssignListTask.class, LOOPS, false);
     }
 
     public void testAssignListCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.value = "foo";
-        java.util.ArrayList<String> c1 = new java.util.ArrayList<String>(2);
-        c1.add("foo");
-        c1.add("bar");
-        args.ca = new Object[]{
-            new String[]{"one", "two"},
-            c1
-        };
-        doAssignList(args, LOOPS, true);
+        runThreaded(AssignListTask.class, LOOPS, true);
     }
 
-    void doCompute(TestCacheArguments x, int loops, boolean cache) throws 
Exception {
-        if (loops == 0) {
-            loops = MIX.length;
-        }
-        if (cache) {
-            jexl.setCache(32);
-        } else {
-            jexl.setCache(0);
-        }
-        Map<String, Object> vars = new HashMap<String,Object>();
-        JexlContext jc = new MapContext(vars);
-        jexl.setDebug(true);
-        Expression compute2 = jexl.createExpression("cache.compute(a0, a1)");
-        Expression compute1 = jexl.createExpression("cache.compute(a0)");
-        Expression compute1null = jexl.createExpression("cache.compute(a0)");
-        Expression ambiguous = jexl.createExpression("cache.ambiguous(a0, 
a1)");
-        jexl.setDebug(false);
-        Object result = null;
-        String expected = null;
-        for (int l = 0; l < loops; ++l) {
-            int mix = MIX[l % MIX.length] % x.ca.length;
+    /**
+     * A task to check method calls.
+     */
+    public static class ComputeTask extends Task {
+        public ComputeTask(int loops) {
+            super(loops);
+        }
+
+        public Integer call() throws Exception {
+            args.ca = new Object[]{args.c0, args.c1, args.c2};
+            args.value = new Object[]{new Integer(2), "quux"};
+            //jexl.setDebug(true);
+            Expression compute2 = jexl.createExpression("cache.compute(a0, 
a1)");
+            Expression compute1 = jexl.createExpression("cache.compute(a0)");
+            Expression compute1null = 
jexl.createExpression("cache.compute(a0)");
+            Expression ambiguous = jexl.createExpression("cache.ambiguous(a0, 
a1)");
+            //jexl.setDebug(false);
+
+            Object result = null;
+            String expected = null;
+            for (int l = 0; l < loops; ++l) {
+                int mix = MIX[l % MIX.length] % args.ca.length;
+                Object value = args.value[l % args.value.length];
+
+                vars.put("cache", args.ca[mix]);
+                if (value instanceof String) {
+                    vars.put("a0", "S0");
+                    vars.put("a1", "S1");
+                    expected = "Cached" + mix + "@s#S0,s#S1";
+                } else if (value instanceof Integer) {
+                    vars.put("a0", Integer.valueOf(7));
+                    vars.put("a1", Integer.valueOf(9));
+                    expected = "Cached" + mix + "@i#7,i#9";
+                } else {
+                    fail("unexpected value type");
+                }
+                result = compute2.evaluate(jc);
+                assertEquals(compute2.toString(), expected, result);
 
-            vars.put("cache", x.ca[mix]);
-            if (x.value instanceof String) {
-                vars.put("a0", "S0");
-                vars.put("a1", "S1");
-                expected = "Cached" + mix + "@s#S0,s#S1";
-            } else if (x.value instanceof Integer) {
-                vars.put("a0", Integer.valueOf(7));
-                vars.put("a1", Integer.valueOf(9));
-                expected = "Cached" + mix + "@i#7,i#9";
-            } else {
-                fail("unexpected value type");
-            }
-            result = compute2.evaluate(jc);
-            assertEquals(compute2.toString(), expected, result);
+                if (value instanceof Integer) {
+                    try {
+                        vars.put("a0", Short.valueOf((short) 17));
+                        vars.put("a1", Short.valueOf((short) 19));
+                        result = ambiguous.evaluate(jc);
+                        fail("should have thrown an exception");
+                    } catch (JexlException xany) {
+                        // throws due to ambiguous exception
+                    }
+                }
+
+                if (value instanceof String) {
+                    vars.put("a0", "X0");
+                    expected = "Cached" + mix + "@s#X0";
+                } else if (value instanceof Integer) {
+                    vars.put("a0", Integer.valueOf(5));
+                    expected = "Cached" + mix + "@i#5";
+                } else {
+                    fail("unexpected value type");
+                }
+                result = compute1.evaluate(jc);
+                assertEquals(compute1.toString(), expected, result);
 
-            if (x.value instanceof Integer) {
                 try {
-                    vars.put("a0", Short.valueOf((short) 17));
-                    vars.put("a1", Short.valueOf((short) 19));
-                    result = ambiguous.evaluate(jc);
+                    vars.put("a0", null);
+                    result = compute1null.evaluate(jc);
                     fail("should have thrown an exception");
                 } catch (JexlException xany) {
                     // throws due to ambiguous exception
+                    String sany = xany.getMessage();
+                    String tname = getClass().getName();
+                    if (!sany.startsWith(tname)) {
+                        fail("debug mode should carry caller information, "
+                                + sany + ", "
+                                + tname);
+                    }
                 }
             }
-
-            if (x.value instanceof String) {
-                vars.put("a0", "X0");
-                expected = "Cached" + mix + "@s#X0";
-            } else if (x.value instanceof Integer) {
-                vars.put("a0", Integer.valueOf(5));
-                expected = "Cached" + mix + "@i#5";
-            } else {
-                fail("unexpected value type");
-            }
-            result = compute1.evaluate(jc);
-            assertEquals(compute1.toString(), expected, result);
-
-            try {
-                vars.put("a0", null);
-                jexl.setDebug(true);
-                result = compute1null.evaluate(jc);
-                fail("should have thrown an exception");
-            } catch (JexlException xany) {
-                // throws due to ambiguous exception
-                String sany = xany.getMessage();
-                String tname = getClass().getName();
-                if (!sany.startsWith(tname)) {
-                    fail("debug mode should carry caller information, "
-                         + sany +", "
-                         + tname);
-                }
-            }
-            finally {
-                jexl.setDebug(false);
-            }
+            return Integer.valueOf(loops);
         }
     }
 
     public void testComputeNoCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.ca = new Object[]{
-                    args.c0, args.c1, args.c2
-                };
-        args.value = new Integer(2);
-        doCompute(args, LOOPS, false);
+        try {
+            jexl.setDebug(true);
+            runThreaded(ComputeTask.class, LOOPS, false);
+        } finally {
+            jexl.setDebug(false);
+        }
     }
 
     public void testComputeCache() throws Exception {
-        TestCacheArguments args = new TestCacheArguments();
-        args.ca = new Object[]{
-                    args.c0, args.c1, args.c2
-                };
-        args.value = new Integer(2);
-        doCompute(args, LOOPS, true);
+        try {
+            jexl.setDebug(true);
+            runThreaded(ComputeTask.class, LOOPS, true);
+        } finally {
+            jexl.setDebug(false);
+        }
     }
 
+    /**
+     * The remaining tests exercise the namespaced functions; not MT.
+     * @param x
+     * @param loops
+     * @param cache
+     * @throws Exception
+     */
     void doCOMPUTE(TestCacheArguments x, int loops, boolean cache) throws 
Exception {
         if (loops == 0) {
             loops = MIX.length;
@@ -512,7 +606,7 @@
         } else {
             jexl.setCache(0);
         }
-        Map<String, Object> vars = new HashMap<String,Object>();
+        Map<String, Object> vars = new HashMap<String, Object>();
         JexlContext jc = new MapContext(vars);
         java.util.Map<String, Object> funcs = new java.util.HashMap<String, 
Object>();
         jexl.setFunctions(funcs);
@@ -522,13 +616,14 @@
         String expected = null;
         for (int l = 0; l < loops; ++l) {
             int mix = MIX[l % MIX.length] % x.ca.length;
+            Object value = x.value[l % x.value.length];
 
             funcs.put("cached", x.ca[mix]);
-            if (x.value instanceof String) {
+            if (value instanceof String) {
                 vars.put("a0", "S0");
                 vars.put("a1", "S1");
                 expected = "cac...@s#s0,s#S1";
-            } else if (x.value instanceof Integer) {
+            } else if (value instanceof Integer) {
                 vars.put("a0", Integer.valueOf(7));
                 vars.put("a1", Integer.valueOf(9));
                 expected = "cac...@i#7,i#9";
@@ -538,10 +633,10 @@
             result = compute2.evaluate(jc);
             assertEquals(compute2.toString(), expected, result);
 
-            if (x.value instanceof String) {
+            if (value instanceof String) {
                 vars.put("a0", "X0");
                 expected = "cac...@s#x0";
-            } else if (x.value instanceof Integer) {
+            } else if (value instanceof Integer) {
                 vars.put("a0", Integer.valueOf(5));
                 expected = "cac...@i#5";
             } else {
@@ -555,19 +650,18 @@
     public void testCOMPUTENoCache() throws Exception {
         TestCacheArguments args = new TestCacheArguments();
         args.ca = new Object[]{
-            Cached.class, Cached1.class, Cached2.class
-        };
-        args.value = new Integer(2);
+                    Cached.class, Cached1.class, Cached2.class
+                };
+        args.value = new Object[]{new Integer(2), "quux"};
         doCOMPUTE(args, LOOPS, false);
     }
-    
+
     public void testCOMPUTECache() throws Exception {
         TestCacheArguments args = new TestCacheArguments();
         args.ca = new Object[]{
-            Cached.class, Cached1.class, Cached2.class
-        };
-        args.value = new Integer(2);
+                    Cached.class, Cached1.class, Cached2.class
+                };
+        args.value = new Object[]{new Integer(2), "quux"};
         doCOMPUTE(args, LOOPS, true);
     }
-
 }


Reply via email to