blob: 96c88f1851f5c864cc0691fda5349015de5f8683 [file] [log] [blame]
/*
* 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.commons.jexl2;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import org.apache.commons.jexl2.parser.JexlNode;
import org.apache.commons.jexl2.parser.ASTJexlScript;
import junit.framework.TestCase;
/**
* Implements runTest methods to dynamically instantiate and invoke a test,
* wrapping the call with setUp(), tearDown() calls.
* Eases the implementation of main methods to debug.
*/
public class JexlTestCase extends TestCase {
/** No parameters signature for test run. */
private static final Class<?>[] noParms = {};
/** String parameter signature for test run. */
private static final Class<?>[] stringParm = {String.class};
/** A default Jexl engine instance. */
protected final JexlEngine JEXL;
public JexlTestCase(String name) {
this(name, new JexlEngine());
}
protected JexlTestCase(String name, JexlEngine jexl) {
super(name);
JEXL = jexl;
JEXL.setCache(512);
}
public JexlTestCase() {
this(new JexlEngine());
}
protected JexlTestCase(JexlEngine jexl) {
super();
JEXL = jexl;
JEXL.setCache(512);
}
@Override
protected void tearDown() throws Exception {
debuggerCheck(JEXL);
}
/**
* Will force testing the debugger for each derived test class by
* recreating each expression from the JexlNode in the JexlEngine cache &
* testing them for equality with the origin.
* @throws Exception
*/
public static void debuggerCheck(JexlEngine jexl) throws Exception {
// without a cache, nothing to check
if (jexl.cache == null) {
return;
}
JexlEngine jdbg = new JexlEngine();
Debugger dbg = new Debugger();
// iterate over all expression in cache
Iterator<Map.Entry<String,ASTJexlScript>> inodes = jexl.cache.entrySet().iterator();
while (inodes.hasNext()) {
Map.Entry<String,ASTJexlScript> entry = inodes.next();
JexlNode node = entry.getValue();
// recreate expr string from AST
dbg.debug(node);
String expressiondbg = dbg.data();
// recreate expr from string
Expression exprdbg = jdbg.createExpression(expressiondbg);
// make arg cause become the root cause
JexlNode root = ((ExpressionImpl) exprdbg).script;
while (root.jjtGetParent() != null) {
root = root.jjtGetParent();
}
// test equality
String reason = JexlTestCase.checkEquals(root, node);
if (reason != null) {
throw new RuntimeException("debugger equal failed: "
+ expressiondbg
+" /**** " +reason+" **** */ "
+ entry.getKey());
}
}
}
/**
* Creates a list of all descendants of a script including itself.
* @param script the script to flatten
* @return the descendants-and-self list
*/
private static ArrayList<JexlNode> flatten(JexlNode node) {
ArrayList<JexlNode> list = new ArrayList<JexlNode>();
flatten(list, node);
return list;
}
/**
* Recursively adds all children of a script to the list of descendants.
* @param list the list of descendants to add to
* @param script the script & descendants to add
*/
private static void flatten(List<JexlNode> list, JexlNode node) {
int nc = node.jjtGetNumChildren();
list.add(node);
for(int c = 0; c < nc; ++c) {
flatten(list, node.jjtGetChild(c));
}
}
/**
* Checks the equality of 2 nodes by comparing all their descendants.
* Descendants must have the same class and same image if non null.
* @param lhs the left script
* @param rhs the right script
* @return null if true, a reason otherwise
*/
private static String checkEquals(JexlNode lhs, JexlNode rhs) {
if (lhs != rhs) {
ArrayList<JexlNode> lhsl = flatten(lhs);
ArrayList<JexlNode> rhsl = flatten(rhs);
if (lhsl.size() != rhsl.size()) {
return "size: " + lhsl.size() + " != " + rhsl.size();
}
for(int n = 0; n < lhsl.size(); ++n) {
lhs = lhsl.get(n);
rhs = rhsl.get(n);
if (lhs.getClass() != rhs.getClass()) {
return "class: " + lhs.getClass() + " != " + rhs.getClass();
}
if ((lhs.image == null && rhs.image != null)
|| (lhs.image != null && rhs.image == null)) {
return "image: " + lhs.image + " != " + rhs.image;
}
if (lhs.image != null && !lhs.image.equals(rhs.image)) {
return "image: " + lhs.image + " != " + rhs.image;
}
}
}
return null;
}
/**
* Dynamically runs a test method.
* @param name the test method to run
* @throws Exception if anything goes wrong
*/
public void runTest(String name) throws Exception {
if ("runTest".equals(name)) {
return;
}
Method method = null;
try {
method = this.getClass().getDeclaredMethod(name, noParms);
}
catch(Exception xany) {
fail("no such test: " + name);
return;
}
try {
this.setUp();
method.invoke(this);
} finally {
this.tearDown();
}
}
/**
* Instantiate and runs a test method; useful for debugging purpose.
* For instance:
* <code>
* public static void main(String[] args) throws Exception {
* runTest("BitwiseOperatorTest","testAndVariableNumberCoercion");
* }
* </code>
* @param tname the test class name
* @param mname the test class method
* @throws Exception
*/
public static void runTest(String tname, String mname) throws Exception {
String testClassName = "org.apache.commons.jexl2."+tname;
Class<JexlTestCase> clazz = null;
JexlTestCase test = null;
// find the class
try {
clazz = (Class<JexlTestCase>) Class.forName(testClassName);
}
catch(ClassNotFoundException xclass) {
fail("no such class: " + testClassName);
return;
}
// find ctor & instantiate
Constructor<JexlTestCase> ctor = null;
try {
ctor = clazz.getConstructor(stringParm);
test = ctor.newInstance("debug");
}
catch(NoSuchMethodException xctor) {
// instantiate default class ctor
try {
test = clazz.newInstance();
}
catch(Exception xany) {
fail("cant instantiate test: " + xany);
return;
}
}
catch(Exception xany) {
fail("cant instantiate test: " + xany);
return;
}
// Run the test
test.runTest(mname);
}
/**
* Runs a test.
* @param args where args[0] is the test class name and args[1] the test class method
* @throws Exception
*/
public static void main(String[] args) throws Exception {
runTest(args[0], args[1]);
}
}