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.jexl3;
19  
20  import static org.junit.jupiter.api.Assertions.assertThrows;
21  import static org.junit.jupiter.api.Assertions.fail;
22  
23  import java.lang.reflect.Constructor;
24  import java.lang.reflect.Method;
25  import java.util.Arrays;
26  
27  import org.apache.commons.jexl3.internal.Debugger;
28  import org.apache.commons.jexl3.internal.OptionsContext;
29  import org.apache.commons.jexl3.internal.Util;
30  import org.apache.commons.jexl3.internal.introspection.Uberspect;
31  import org.apache.commons.jexl3.introspection.JexlPermissions;
32  import org.junit.jupiter.api.AfterEach;
33  
34  /**
35   * Implements runTest methods to dynamically instantiate and invoke a test,
36   * wrapping the call with setUp(), tearDown() calls.
37   * Eases the implementation of main methods to debug.
38   */
39  public class JexlTestCase {
40      public static class PragmaticContext extends OptionsContext implements JexlContext.PragmaProcessor, JexlContext.OptionsHandle {
41          private final JexlOptions options;
42  
43          public PragmaticContext() {
44              this(new JexlOptions());
45          }
46  
47          public PragmaticContext(final JexlOptions o) {
48              this.options = o;
49          }
50  
51          @Override
52          public JexlOptions getEngineOptions() {
53              return options;
54          }
55  
56          @Override
57          public void processPragma(final JexlOptions opts, final String key, final Object value) {
58              if ("script.mode".equals(key) && "pro50".equals(value)) {
59                  opts.set(MODE_PRO50);
60              }
61          }
62  
63          @Override
64          public void processPragma(final String key, final Object value) {
65              processPragma(null, key, value);
66          }
67      }
68      // The default options: all tests where engine lexicality is
69      // important can be identified by the builder  calling lexical(...).
70      static {
71          JexlOptions.setDefaultFlags("-safe", "+lexical");
72      }
73      /** No parameters signature for test run. */
74      private static final Class<?>[] NO_PARMS = {};
75  
76      /** String parameter signature for test run. */
77      private static final Class<?>[] STRING_PARM = {String.class};
78  
79      // define mode pro50
80      static final JexlOptions MODE_PRO50 = new JexlOptions();
81  
82      static {
83          MODE_PRO50.setFlags( "+strict +cancellable +lexical +lexicalShade -safe".split(" "));
84      }
85  
86      /**
87       * A very secure singleton.
88       */
89      public static final JexlPermissions SECURE = JexlPermissions.RESTRICTED;
90  
91      static JexlEngine createEngine() {
92          return new JexlBuilder().create();
93      }
94  
95      public static JexlEngine createEngine(final boolean lenient) {
96          return createEngine(lenient, SECURE);
97      }
98      public static JexlEngine createEngine(final boolean lenient, final JexlPermissions permissions) {
99          return new JexlBuilder()
100                 .uberspect(new Uberspect(null, null, permissions))
101                 .arithmetic(new JexlArithmetic(!lenient)).cache(128).create();
102     }
103 
104     static JexlEngine createEngine(final JexlFeatures features) {
105         return new JexlBuilder().features(features).create();
106     }
107     /**
108      * Will force testing the debugger for each derived test class by
109      * recreating each expression from the JexlNode in the JexlEngine cache &
110      * testing them for equality with the origin.
111      * @throws Exception
112      */
113     public static void debuggerCheck(final JexlEngine ijexl) throws Exception {
114          Util.debuggerCheck(ijexl);
115     }
116 
117     /**
118      * Compare strings ignoring white space differences.
119      * <p>This replaces any sequence of whitespaces (ie \\s) by one space (ie ASCII 32) in both
120      * arguments then compares them.</p>
121      * @param lhs left hand side
122      * @param rhs right hand side
123      * @return true if strings are equal besides whitespace
124      */
125     public static boolean equalsIgnoreWhiteSpace(final String lhs, final String rhs) {
126         final String lhsw = lhs.trim().replaceAll("\\s+", "");
127         final String rhsw = rhs.trim().replaceAll("\\s+", "");
128         return lhsw.equals(rhsw);
129     }
130 
131     /**
132      * Runs a test.
133      * @param args where args[0] is the test class name and args[1] the test class method
134      * @throws Exception
135      */
136     public static void main(final String[] args) throws Exception {
137         runTest(args[0], args[1]);
138     }
139 
140     /**
141      * Instantiate and runs a test method; useful for debugging purpose.
142      * For instance:
143      * <code>
144      * public static void main(String[] args) throws Exception {
145      *   runTest("BitwiseOperatorTest","testAndVariableNumberCoercion");
146      * }
147      * </code>
148      * @param tname the test class name
149      * @param mname the test class method
150      * @throws Exception
151      */
152     public static void runTest(final String tname, final String mname) throws Exception {
153         final String testClassName = "org.apache.commons.jexl3." + tname;
154         final Class<JexlTestCase> clazz = null;
155         JexlTestCase test = null;
156         // find the class
157         assertThrows(ClassNotFoundException.class, () -> Class.forName(testClassName), () -> "no such class: " + testClassName);
158         // find ctor & instantiate
159         Constructor<JexlTestCase> ctor = null;
160         try {
161             ctor = clazz.getConstructor(STRING_PARM);
162             test = ctor.newInstance("debug");
163         } catch (final NoSuchMethodException xctor) {
164             // instantiate default class ctor
165             try {
166                 test = clazz.getConstructor().newInstance();
167             } catch (final Exception xany) {
168                 fail("cant instantiate test: " + xany);
169                 return;
170             }
171         } catch (final Exception xany) {
172             fail("cant instantiate test: " + xany);
173             return;
174         }
175         // Run the test
176         test.runTest(mname);
177     }
178 
179     public static String toString(final JexlScript script) {
180         final Debugger d = new Debugger().lineFeed("").indentation(0);
181         d.debug(script);
182         return  d.toString();
183     }
184 
185     /** A default JEXL engine instance. */
186     protected final JexlEngine JEXL;
187 
188     public JexlTestCase(final String name) {
189         this(name, new JexlBuilder().imports(Arrays.asList("java.lang","java.math")).permissions(null).cache(128).create());
190     }
191 
192     protected JexlTestCase(final String name, final JexlEngine jexl) {
193         //super(name);
194         JEXL = jexl;
195     }
196 
197     /**
198      * Dynamically runs a test method.
199      * @param name the test method to run
200      * @throws Exception if anything goes wrong
201      */
202     public void runTest(final String name) throws Exception {
203         if ("runTest".equals(name)) {
204             return;
205         }
206         Method method = null;
207         try {
208             method = this.getClass().getDeclaredMethod(name, NO_PARMS);
209         }
210         catch (final Exception xany) {
211             fail("no such test: " + name);
212             return;
213         }
214         try {
215             setUp();
216             method.invoke(this);
217         } finally {
218             tearDown();
219         }
220     }
221 
222     public void setUp() throws Exception {
223         // nothing to do
224     }
225 
226     public String simpleWhitespace(final String arg) {
227         return arg.trim().replaceAll("\\s+", " ");
228     }
229 
230     @AfterEach
231     public void tearDown() throws Exception {
232         debuggerCheck(JEXL);
233     }
234 }