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    *      https://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  
74      /** No parameters signature for test run. */
75      private static final Class<?>[] NO_PARMS = {};
76  
77      /** String parameter signature for test run. */
78      private static final Class<?>[] STRING_PARM = {String.class};
79  
80      // define mode pro50
81      static final JexlOptions MODE_PRO50 = new JexlOptions();
82  
83      static {
84          MODE_PRO50.setFlags("+strict +cancellable +lexical +lexicalShade -safe".split(" "));
85      }
86  
87      /**
88       * A very secure singleton.
89       */
90      public static final JexlPermissions SECURE = JexlPermissions.RESTRICTED;
91  
92      static JexlEngine createEngine() {
93          return new JexlBuilder().create();
94      }
95  
96      public static JexlEngine createEngine(final boolean lenient) {
97          return createEngine(lenient, SECURE);
98      }
99      public static JexlEngine createEngine(final boolean lenient, final JexlPermissions permissions) {
100         return new JexlBuilder()
101                 .uberspect(new Uberspect(null, null, permissions))
102                 .arithmetic(new JexlArithmetic(!lenient)).cache(128).create();
103     }
104 
105     static JexlEngine createEngine(final JexlFeatures features) {
106         return new JexlBuilder().features(features).create();
107     }
108 
109     /**
110      * Will force testing the debugger for each derived test class by
111      * recreating each expression from the JexlNode in the JexlEngine cache &
112      * testing them for equality with the origin.
113      * @throws Exception
114      */
115     public static void debuggerCheck(final JexlEngine ijexl) throws Exception {
116          Util.debuggerCheck(ijexl);
117     }
118 
119     /**
120      * Compare strings ignoring white space differences.
121      * <p>This replaces any sequence of whitespaces (ie \\s) by one space (ie ASCII 32) in both
122      * arguments then compares them.</p>
123      * @param lhs left hand side
124      * @param rhs right hand side
125      * @return true if strings are equal besides whitespace
126      */
127     public static boolean equalsIgnoreWhiteSpace(final String lhs, final String rhs) {
128         final String lhsw = lhs.trim().replaceAll("\\s+", "");
129         final String rhsw = rhs.trim().replaceAll("\\s+", "");
130         return lhsw.equals(rhsw);
131     }
132 
133     /**
134      * Runs a test.
135      * @param args where args[0] is the test class name and args[1] the test class method
136      * @throws Exception
137      */
138     public static void main(final String[] args) throws Exception {
139         runTest(args[0], args[1]);
140     }
141 
142     /**
143      * Instantiate and runs a test method; useful for debugging purpose.
144      * For instance:
145      * <code>
146      * public static void main(String[] args) throws Exception {
147      *   runTest("BitwiseOperatorTest","testAndVariableNumberCoercion");
148      * }
149      * </code>
150      * @param tname the test class name
151      * @param mname the test class method
152      * @throws Exception
153      */
154     public static void runTest(final String tname, final String mname) throws Exception {
155         final String testClassName = "org.apache.commons.jexl3." + tname;
156         final Class<JexlTestCase> clazz = null;
157         JexlTestCase test = null;
158         // find the class
159         assertThrows(ClassNotFoundException.class, () -> Class.forName(testClassName), () -> "no such class: " + testClassName);
160         // find ctor & instantiate
161         Constructor<JexlTestCase> ctor = null;
162         try {
163             ctor = clazz.getConstructor(STRING_PARM);
164             test = ctor.newInstance("debug");
165         } catch (final NoSuchMethodException xctor) {
166             // instantiate default class ctor
167             try {
168                 test = clazz.getConstructor().newInstance();
169             } catch (final Exception xany) {
170                 fail("cant instantiate test: " + xany);
171                 return;
172             }
173         } catch (final Exception xany) {
174             fail("cant instantiate test: " + xany);
175             return;
176         }
177         // Run the test
178         test.runTest(mname);
179     }
180 
181     public static String toString(final JexlScript script) {
182         final Debugger d = new Debugger().lineFeed("").indentation(0);
183         d.debug(script);
184         return  d.toString();
185     }
186 
187     /** A default JEXL engine instance. */
188     protected final JexlEngine JEXL;
189 
190     public JexlTestCase(final String name) {
191         this(name, new JexlBuilder().imports(Arrays.asList("java.lang","java.math")).permissions(null).cache(128).create());
192     }
193 
194     protected JexlTestCase(final String name, final JexlEngine jexl) {
195         //super(name);
196         JEXL = jexl;
197     }
198 
199     /**
200      * Dynamically runs a test method.
201      * @param name the test method to run
202      * @throws Exception if anything goes wrong
203      */
204     public void runTest(final String name) throws Exception {
205         if ("runTest".equals(name)) {
206             return;
207         }
208         Method method = null;
209         try {
210             method = this.getClass().getDeclaredMethod(name, NO_PARMS);
211         }
212         catch (final Exception xany) {
213             fail("no such test: " + name);
214             return;
215         }
216         try {
217             setUp();
218             method.invoke(this);
219         } finally {
220             tearDown();
221         }
222     }
223 
224     public void setUp() throws Exception {
225         // nothing to do
226     }
227 
228     public String simpleWhitespace(final String arg) {
229         return arg.trim().replaceAll("\\s+", " ");
230     }
231 
232     @AfterEach
233     public void tearDown() throws Exception {
234         debuggerCheck(JEXL);
235     }
236 }