View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jexl2; 
19  
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.Method;
22  import java.util.Iterator;
23  import java.util.Map;
24  import java.util.List;
25  import java.util.ArrayList;
26  
27  import org.apache.commons.jexl2.parser.JexlNode;
28  import org.apache.commons.jexl2.parser.ASTJexlScript;
29  
30  import junit.framework.TestCase;
31  
32  /**
33   * Implements runTest methods to dynamically instantiate and invoke a test,
34   * wrapping the call with setUp(), tearDown() calls.
35   * Eases the implementation of main methods to debug.
36   */
37  public class JexlTestCase extends TestCase {
38      /** No parameters signature for test run. */
39      private static final Class<?>[] noParms = {};
40      /** String parameter signature for test run. */
41      private static final Class<?>[] stringParm = {String.class};
42  
43      /** A default Jexl engine instance. */
44      protected final JexlEngine JEXL;
45  
46      public JexlTestCase(String name) {
47          this(name, new JexlEngine());
48      }
49      protected JexlTestCase(String name, JexlEngine jexl) {
50          super(name);
51          JEXL = jexl;
52          JEXL.setCache(512);
53      }
54      public JexlTestCase() {
55          this(new JexlEngine());
56      }
57      protected JexlTestCase(JexlEngine jexl) {
58          super();
59          JEXL = jexl;
60          JEXL.setCache(512);
61      }
62  
63      @Override
64      protected void tearDown() throws Exception {
65          debuggerCheck(JEXL);
66      }
67  
68      public static JexlEngine createEngine(boolean lenient) {
69          return new JexlEngine(null, new JexlArithmetic(lenient), null, null);
70      }
71  
72      public static JexlEngine createThreadedArithmeticEngine(boolean lenient) {
73          return new JexlEngine(null, new JexlThreadedArithmetic(lenient), null, null);
74      }
75      
76      /**
77       * Will force testing the debugger for each derived test class by
78       * recreating each expression from the JexlNode in the JexlEngine cache &
79       * testing them for equality with the origin.
80       * @throws Exception
81       */
82      public static void debuggerCheck(JexlEngine jexl) throws Exception {
83          // without a cache, nothing to check
84          if (jexl.cache == null) {
85              return;
86          }
87          JexlEngine jdbg = new JexlEngine();
88          jdbg.parser.ALLOW_REGISTERS = true;
89          Debugger dbg = new Debugger();
90          // iterate over all expression in cache
91          Iterator<Map.Entry<String,ASTJexlScript>> inodes = jexl.cache.entrySet().iterator();
92          while (inodes.hasNext()) {
93              Map.Entry<String,ASTJexlScript> entry = inodes.next();
94              JexlNode node = entry.getValue();
95              // recreate expr string from AST
96              dbg.debug(node);
97              String expressiondbg = dbg.data();
98              // recreate expr from string
99              Script exprdbg = jdbg.createScript(expressiondbg);
100             // make arg cause become the root cause
101             JexlNode root = ((ExpressionImpl) exprdbg).script;
102             while (root.jjtGetParent() != null) {
103                 root = root.jjtGetParent();
104             }
105             // test equality
106             String reason = JexlTestCase.checkEquals(root, node);
107             if (reason != null) {
108                 throw new RuntimeException("debugger equal failed: "
109                                            + expressiondbg
110                                            +" /**** "  +reason+" **** */ "
111                                            + entry.getKey());
112             }
113         }
114     }
115 
116     /**
117      * Creates a list of all descendants of a script including itself.
118      * @param script the script to flatten
119      * @return the descendants-and-self list
120      */
121     protected static ArrayList<JexlNode> flatten(JexlNode node) {
122         ArrayList<JexlNode> list = new ArrayList<JexlNode>();
123         flatten(list, node);
124         return list;
125     }
126 
127     /**
128      * Recursively adds all children of a script to the list of descendants.
129      * @param list the list of descendants to add to
130      * @param script the script & descendants to add
131      */
132     private static void flatten(List<JexlNode> list, JexlNode node) {
133         int nc = node.jjtGetNumChildren();
134         list.add(node);
135         for(int c = 0; c < nc; ++c) {
136             flatten(list, node.jjtGetChild(c));
137         }
138     }
139 
140     /**
141      * Checks the equality of 2 nodes by comparing all their descendants.
142      * Descendants must have the same class and same image if non null.
143      * @param lhs the left script
144      * @param rhs the right script
145      * @return null if true, a reason otherwise
146      */
147     private static String checkEquals(JexlNode lhs, JexlNode rhs) {
148         if (lhs != rhs) {
149             ArrayList<JexlNode> lhsl = flatten(lhs);
150             ArrayList<JexlNode> rhsl = flatten(rhs);
151             if (lhsl.size() != rhsl.size()) {
152                  return "size: " + lhsl.size() + " != " + rhsl.size();
153             }
154             for(int n = 0; n < lhsl.size(); ++n) {
155                 lhs = lhsl.get(n);
156                 rhs = rhsl.get(n);
157                 if (lhs.getClass() != rhs.getClass()) {
158                     return "class: " + lhs.getClass() + " != " + rhs.getClass();
159                 }
160                 if ((lhs.image == null && rhs.image != null)
161                     || (lhs.image != null && rhs.image == null)) {
162                     return "image: " + lhs.image + " != " + rhs.image;
163                 }
164                 if (lhs.image != null && !lhs.image.equals(rhs.image)) {
165                     return "image: " + lhs.image + " != " + rhs.image;
166                 }
167             }
168         }
169         return null;
170     }
171     
172     /**
173      * A helper class to help debug AST problems.
174      * @param e the script
175      * @return an indented version of the AST
176      */
177     protected String flattenedStr(Script e) {
178         return e.getText() + "\n" + flattenedStr(((ExpressionImpl) e).script);
179     }
180 
181     static private String indent(JexlNode node) {
182         StringBuilder strb = new StringBuilder();
183         while (node != null) {
184             strb.append("  ");
185             node = node.jjtGetParent();
186         }
187         return strb.toString();
188     }
189 
190 
191     private String flattenedStr(JexlNode node) {
192         ArrayList<JexlNode> flattened = flatten(node);
193         StringBuilder strb = new StringBuilder();
194         for (JexlNode flat : flattened) {
195             strb.append(indent(flat));
196             strb.append(flat.getClass().getSimpleName());
197             if (flat.image != null) {
198                 strb.append(" = ");
199                 strb.append(flat.image);
200             }
201             strb.append("\n");
202         }
203         return strb.toString();
204     }
205     /**
206      * Dynamically runs a test method.
207      * @param name the test method to run
208      * @throws Exception if anything goes wrong
209      */
210     public void runTest(String name) throws Exception {
211         if ("runTest".equals(name)) {
212             return;
213         }
214         Method method = null;
215         try {
216             method = this.getClass().getDeclaredMethod(name, noParms);
217         }
218         catch(Exception xany) {
219             fail("no such test: " + name);
220             return;
221         }
222         try {
223             this.setUp();
224             method.invoke(this);
225         } finally {
226             this.tearDown();
227         }
228     }
229 
230     /**
231      * Instantiate and runs a test method; useful for debugging purpose.
232      * For instance:
233      * <code>
234      * public static void main(String[] args) throws Exception {
235      *   runTest("BitwiseOperatorTest","testAndVariableNumberCoercion");
236      * }
237      * </code>
238      * @param tname the test class name
239      * @param mname the test class method
240      * @throws Exception
241      */
242     public static void runTest(String tname, String mname) throws Exception {
243         String testClassName = "org.apache.commons.jexl2."+tname;
244         Class<JexlTestCase> clazz = null;
245         JexlTestCase test = null;
246         // find the class
247         try {
248             clazz = (Class<JexlTestCase>) Class.forName(testClassName);
249         }
250         catch(ClassNotFoundException xclass) {
251             fail("no such class: " + testClassName);
252             return;
253         }
254         // find ctor & instantiate
255         Constructor<JexlTestCase> ctor = null;
256         try {
257             ctor = clazz.getConstructor(stringParm);
258             test = ctor.newInstance("debug");
259         }
260         catch(NoSuchMethodException xctor) {
261             // instantiate default class ctor
262             try {
263                 test = clazz.newInstance();
264             }
265             catch(Exception xany) {
266                 fail("cant instantiate test: " + xany);
267                 return;
268             }
269         }
270         catch(Exception xany) {
271             fail("cant instantiate test: " + xany);
272             return;
273         }
274         // Run the test
275         test.runTest(mname);
276     }
277 
278     /**
279      * Runs a test.
280      * @param args where args[0] is the test class name and args[1] the test class method
281      * @throws Exception
282      */
283     public static void main(String[] args) throws Exception {
284         runTest(args[0], args[1]);
285     }
286 }