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.scxml2.env.javascript;
19  
20  import java.io.StringReader;
21  
22  import javax.xml.xpath.XPath;
23  import javax.xml.xpath.XPathConstants;
24  import javax.xml.xpath.XPathFactory;
25  
26  import org.apache.commons.scxml2.Context;
27  import org.apache.commons.scxml2.Evaluator;
28  import org.apache.commons.scxml2.SCXMLExecutor;
29  import org.apache.commons.scxml2.SCXMLExpressionException;
30  import org.apache.commons.scxml2.SCXMLTestHelper;
31  import org.apache.commons.scxml2.io.SCXMLReader;
32  import org.junit.Assert;
33  import org.junit.Before;
34  import org.junit.Test;
35  import org.w3c.dom.Element;
36  import org.w3c.dom.Node;
37  
38  /** JUnit 3 test case for the JSEvaluator expression evaluator
39   *  class. Includes basic tests for:
40   *  <ul>
41   *  <li> constructor
42   *  <li> simple standard Javascript expressions
43   *  <li> Javascript expressions referencing SCXML &lt;var..&gt; variables.
44   *  <li> Javascript expressions referencing SCXML data model elements.
45   *  <li> Javascript expressions referencing SCXML data model locations.
46   *  <li> Javascript functions referencing SCXML context variables.
47   *  </ul>
48   */
49  
50  public class JSEvaluatorTest {
51      // TEST CONSTANTS
52  
53      private static final String BAD_EXPRESSION = ">";
54      private static final String SCRIPT         = "<?xml version='1.0'?>" +
55                                                   "<scxml xmlns        = 'http://www.w3.org/2005/07/scxml' " +
56                                                          "xmlns:scxml  = 'http://commons.apache.org/scxml' " +
57                                                          "datamodel = 'ecmascript' " +
58                                                          "initial = 'start' "  +
59                                                          "version      = '1.0'>" +
60                                                    "<datamodel>"           +
61                                                    "<data id='forest'>"  +
62                                                     "<tree xmlns=''>"      +
63                                                     "<branch>"             +
64                                                     "<twig>leaf</twig>"    +
65                                                     "</branch>"            +
66                                                     "</tree>"              +
67                                                    "</data>"               +
68                                                    "</datamodel>"          +
69                                                    "<state id='start'>"              +
70                                                    "<transition target='end' />"     +
71                                                    "</state>"                        +
72                                                    "<state id='end' final='true' />" +
73                                                    "</scxml>";
74  
75      private static final TestItem[] SIMPLE_EXPRESSIONS = {
76              new TestItem("'FIB: ' + (1 + 1 + 2 + 3 + 5)",new String("FIB: 12")),
77              new TestItem("1 + 1 + 2 + 3 + 5",            new Integer(12)), // Force comparison using intValue
78              new TestItem("1.1 + 1.1 + 2.1 + 3.1 + 5.1",  new Double(12.5)),
79              new TestItem("(1 + 1 + 2 + 3 + 5) == 12",    new Boolean(true)),
80              new TestItem("(1 + 1 + 2 + 3 + 5) == 13",    new Boolean(false)),
81      };
82  
83      private static final TestItem[] VAR_EXPRESSIONS = {
84              new TestItem("'FIB: ' + fibonacci",new String("FIB: 12")),
85              new TestItem("fibonacci * 2",      new Double(24)),
86      };
87  
88      private static final String FUNCTION = "function factorial(N) {\r\n" +
89                                                          "if (N == 1)\r\n"    +
90                                                          "   return N;\r\n"   +
91                                                          "\r\n"               +
92                                                          "return N * factorial(N-1);\r\n" +
93                                                  "};\r\n" +
94                                                  "\r\n"   +
95                                                  "function fact5() {\r\n" +
96                                                  "         return factorial(FIVE);\r\n" +
97                                                  "};\r\n" +
98                                                  "\r\n" +
99                                                  "fact5()";
100 
101     // TEST VARIABLES
102 
103     private Context       context;
104     private Evaluator     evaluator;
105     private SCXMLExecutor fsm;
106 
107     // TEST SETUP
108 
109     /**
110      * Creates and initialises an SCXML data model in the context.
111      *
112      */
113     @Before
114     public void setUp() throws Exception {
115             fsm = SCXMLTestHelper.getExecutor(SCXMLReader.read(new StringReader(SCRIPT)));
116             evaluator = fsm.getEvaluator();
117             context = fsm.getGlobalContext();
118             context.set(Context.NAMESPACES_KEY,null);
119     }
120 
121     // CLASS METHODS
122 
123     /**
124      * Standalone test runtime.
125      *
126      */
127     public static void main(String args[]) {
128         String[] testCaseName = {JSEvaluatorTest.class.getName()};
129 
130         junit.textui.TestRunner.main(testCaseName);
131     }
132 
133     // INSTANCE METHOD TESTS
134 
135     /**
136      * Ensures implementation of JSEvaluator default constructor and test basic
137      * expression evaluation.
138      *
139      */    
140     @Test
141     public void testBasic() throws SCXMLExpressionException {
142         Evaluator evaluator = new JSEvaluator();
143 
144         Assert.assertNotNull(evaluator);
145         Assert.assertTrue   (((Boolean) evaluator.eval(context, "1+1 == 2")).booleanValue());
146     }
147 
148     @Test
149     public void testScript() throws SCXMLExpressionException {
150         Evaluator evaluator = new JSEvaluator();
151         context.set("x", 3);
152         context.set("y", 0);
153         String script = 
154             "if ((x * 2.0) == 5.0) {" +
155                 "y = 1.0;\n" +
156             "} else {\n" +
157                 "y = 2.0;\n" +
158             "}";
159         Assert.assertEquals(2.0, evaluator.evalScript(context, script));
160         Assert.assertEquals(2.0, context.get("y"));
161     }
162 
163     /**
164      * Tests handling of illegal expressions.
165      *
166      */    
167     @Test
168     public void testIllegalExpresssion() {
169         Evaluator evaluator = new JSEvaluator();
170 
171         Assert.assertNotNull(evaluator);
172 
173         try {
174             evaluator.eval(context,BAD_EXPRESSION);
175             Assert.fail          ("JSEvaluator should throw SCXMLExpressionException");
176 
177         } catch (SCXMLExpressionException x) {
178             Assert.assertTrue("JSEvaluator: Incorrect error message",
179                        x.getMessage().startsWith("Error evaluating ['" + BAD_EXPRESSION + "']"));
180         }
181     }
182 
183     /**
184      * Tests evaluation with simple standard expressions.
185      *
186      */    
187     @Test
188     public void testStandardExpressions() throws Exception {
189         for (TestItem item: SIMPLE_EXPRESSIONS) {
190             Object eval = evaluator.eval(context,item.expression);
191             if (item.result instanceof Integer && eval instanceof Number) {
192                 Assert.assertEquals("Invalid result: " + item.expression,
193                         ((Integer) item.result).intValue(),
194                         ((Number) eval).intValue());
195             } else {
196                 Assert.assertEquals("Invalid result: " + item.expression,
197                         item.result,
198                         eval);
199             }
200         }
201     }
202 
203     /**
204      * Tests evaluation with SCXML context variables.
205      *
206      */    
207     @Test
208     public void testVarExpressions() throws Exception {
209         context.set("fibonacci",Integer.valueOf(12));
210 
211         for (TestItem item: VAR_EXPRESSIONS) {
212             Assert.assertNotNull(context.get("fibonacci"));
213             Assert.assertEquals (Integer.valueOf(12),context.get("fibonacci"));
214             Assert.assertEquals ("Invalid result: " + item.expression,
215                           item.result,
216                           evaluator.eval(context,item.expression));
217         }
218     }
219 
220     /**
221      * Tests evaluation with invalid SCXML context variables.
222      *
223      */    
224     @Test
225     public void testInvalidVarExpressions() {
226         for (TestItem item: VAR_EXPRESSIONS) {
227             try {
228                 Assert.assertNull    (context.get("fibonacci"));
229                 evaluator.eval(context,item.expression);
230                 Assert.fail          ("Evaluated invalid <var... expression: " + item.expression);
231 
232             } catch (SCXMLExpressionException x) {
233                 // expected, ignore
234             }
235         }
236     }
237 
238     /**
239      * Tests evaluation with SCXML data model expressions.
240      *
241      */    
242     @Test
243     public void testDataModelExpressions() throws Exception {
244         Assert.assertEquals("Invalid result: " + "Data('string($forest/tree/branch/twig)')",
245                      "leaf",
246                      evaluator.eval(context,"Data('string($forest/tree/branch/twig)')"));
247     }
248 
249     /**
250      * Tests evaluation with invalid SCXML data model expressions.
251      *
252      */    
253     @Test
254     public void testInvalidDataModelExpressions() {
255         Assert.assertNull(context.get("forestx"));
256 
257         try {
258             evaluator.eval(context,"Data(forestx,'string($forestx/tree/branch/twig)')");
259             Assert.fail          ("Evaluated invalid Data() expression: " + "Data('string($forestx/tree/branch/twig)')");
260 
261         } catch (SCXMLExpressionException x) {
262             // expected, ignore
263         }
264     }
265 
266     /**
267      * Tests evaluation of SCXML data model locations.
268      *
269      */    
270     @Test
271     public void testDataModelLocations() throws Exception {
272             Assert.assertNotNull(context.get("forest"));
273             XPath  xpath = XPathFactory.newInstance().newXPath();
274             Node   node  = (Node)   context.get("forest");
275             Node   twig  = (Node)   xpath.evaluate("tree/branch/twig", node, XPathConstants.NODE);
276 
277             Assert.assertTrue  ("Invalid result: " + "Data(forest,'$forest/tree/branch/twig')",
278                           evaluator.eval(context,"Data('$forest/tree/branch/twig')") instanceof Element);
279 
280             Assert.assertSame ("Incorrect node returned: " + "Data('$forest/tree/branch/twig')",
281                          twig,
282                          evaluator.eval(context,"Data('$forest/tree/branch/twig')"));
283     }
284 
285     /**
286      * Tests evaluation of invalid SCXML data model locations.
287      *
288      */    
289     @Test
290     public void testInvalidDataModelLocations() throws Exception {
291             Assert.assertNotNull(context.get("forest"));
292             Assert.assertNull("Invalid result: " + "Data('$forest/tree/branch/twigx')",
293                        evaluator.eval(context,"Data('$forest/tree/branch/twigx')"));
294     }
295 
296     /**
297      * Tests evaluation of Javascript functions with variables from SCXML context.
298      *
299      */    
300     @Test
301     public void testScriptFunctions() throws Exception {
302         context.set("FIVE",Integer.valueOf(5));
303         Assert.assertEquals(Integer.valueOf(5),context.get("FIVE"));
304         Assert.assertEquals("Invalid function result",Double.valueOf(120.0),evaluator.eval(context,FUNCTION));
305     }
306 
307 
308     // INNER CLASSES
309 
310     private static class TestItem {
311         private String expression;
312         private Object result;
313 
314         private TestItem(String expression,Object result) {
315             this.expression = expression;
316             this.result     = result;
317         }
318     }
319 
320 }
321