001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.scxml2.env.javascript; 019 020import java.io.StringReader; 021 022import javax.xml.xpath.XPath; 023import javax.xml.xpath.XPathConstants; 024import javax.xml.xpath.XPathFactory; 025 026import org.apache.commons.scxml2.Context; 027import org.apache.commons.scxml2.Evaluator; 028import org.apache.commons.scxml2.SCXMLExecutor; 029import org.apache.commons.scxml2.SCXMLExpressionException; 030import org.apache.commons.scxml2.SCXMLTestHelper; 031import org.apache.commons.scxml2.io.SCXMLReader; 032import org.junit.Assert; 033import org.junit.Before; 034import org.junit.Test; 035import org.w3c.dom.Element; 036import org.w3c.dom.Node; 037 038/** JUnit 3 test case for the JSEvaluator expression evaluator 039 * class. Includes basic tests for: 040 * <ul> 041 * <li> constructor 042 * <li> simple standard Javascript expressions 043 * <li> Javascript expressions referencing SCXML <var..> variables. 044 * <li> Javascript expressions referencing SCXML data model elements. 045 * <li> Javascript expressions referencing SCXML data model locations. 046 * <li> Javascript functions referencing SCXML context variables. 047 * </ul> 048 */ 049 050public class JSEvaluatorTest { 051 // TEST CONSTANTS 052 053 private static final String BAD_EXPRESSION = ">"; 054 private static final String SCRIPT = "<?xml version='1.0'?>" + 055 "<scxml xmlns = 'http://www.w3.org/2005/07/scxml' " + 056 "xmlns:scxml = 'http://commons.apache.org/scxml' " + 057 "datamodel = 'ecmascript' " + 058 "initial = 'start' " + 059 "version = '1.0'>" + 060 "<datamodel>" + 061 "<data id='forest'>" + 062 "<tree xmlns=''>" + 063 "<branch>" + 064 "<twig>leaf</twig>" + 065 "</branch>" + 066 "</tree>" + 067 "</data>" + 068 "</datamodel>" + 069 "<state id='start'>" + 070 "<transition target='end' />" + 071 "</state>" + 072 "<state id='end' final='true' />" + 073 "</scxml>"; 074 075 private static final TestItem[] SIMPLE_EXPRESSIONS = { 076 new TestItem("'FIB: ' + (1 + 1 + 2 + 3 + 5)",new String("FIB: 12")), 077 new TestItem("1 + 1 + 2 + 3 + 5", new Integer(12)), // Force comparison using intValue 078 new TestItem("1.1 + 1.1 + 2.1 + 3.1 + 5.1", new Double(12.5)), 079 new TestItem("(1 + 1 + 2 + 3 + 5) == 12", new Boolean(true)), 080 new TestItem("(1 + 1 + 2 + 3 + 5) == 13", new Boolean(false)), 081 }; 082 083 private static final TestItem[] VAR_EXPRESSIONS = { 084 new TestItem("'FIB: ' + fibonacci",new String("FIB: 12")), 085 new TestItem("fibonacci * 2", new Double(24)), 086 }; 087 088 private static final String FUNCTION = "function factorial(N) {\r\n" + 089 "if (N == 1)\r\n" + 090 " return N;\r\n" + 091 "\r\n" + 092 "return N * factorial(N-1);\r\n" + 093 "};\r\n" + 094 "\r\n" + 095 "function fact5() {\r\n" + 096 " return factorial(FIVE);\r\n" + 097 "};\r\n" + 098 "\r\n" + 099 "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