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  package org.apache.commons.scxml2;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileOutputStream;
22  import java.io.ObjectInputStream;
23  import java.io.ObjectOutputStream;
24  import java.io.Reader;
25  import java.io.StringReader;
26  import java.net.URL;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.List;
30  import java.util.Set;
31  
32  import javax.xml.parsers.DocumentBuilderFactory;
33  
34  import org.apache.commons.scxml2.env.SimpleDispatcher;
35  import org.apache.commons.scxml2.env.Tracer;
36  import org.apache.commons.scxml2.io.SCXMLReader;
37  import org.apache.commons.scxml2.io.SCXMLReader.Configuration;
38  import org.apache.commons.scxml2.model.CustomAction;
39  import org.apache.commons.scxml2.model.EnterableState;
40  import org.apache.commons.scxml2.model.SCXML;
41  import org.apache.commons.scxml2.model.TransitionTarget;
42  import org.junit.Assert;
43  import org.w3c.dom.Document;
44  import org.xml.sax.InputSource;
45  /**
46   * Helper methods for running SCXML unit tests.
47   */
48  public class SCXMLTestHelper {
49  
50      /**
51       * Serialized Commons SCXML object model temporary store.
52       * Assumes the default build artifacts are generated in the
53       * "target" directory (so it can be removed via a clean build).
54       */
55      public static final String SERIALIZATION_DIR = "target/serialization";
56      public static final String SERIALIZATION_FILE_PREFIX = SERIALIZATION_DIR + "/scxml";
57      public static final String SERIALIZATION_FILE_SUFFIX = ".ser";
58  
59      // Generate a unique sequence number for the serialization files
60      private static int sequence=0;
61  
62      private synchronized static String getSequenceNumber() {
63          return Integer.toString(++sequence);
64      }
65  
66      public static URL getResource(String name) {
67          return SCXMLTestHelper.class.getClassLoader().getResource(name);
68      }
69  
70      public static SCXML parse(final String scxmlResource) throws Exception {
71          Assert.assertNotNull(scxmlResource);
72          return parse(getResource(scxmlResource), null);
73      }
74  
75      public static SCXML parse(final URL url) throws Exception {
76          return parse(url, null);
77      }
78  
79      public static SCXML parse(final String scxmlResource, final List<CustomAction> customActions) throws Exception {
80          Assert.assertNotNull(scxmlResource);
81          return parse(getResource(scxmlResource), customActions);
82      }
83  
84      public static SCXML parse(final URL url, final List<CustomAction> customActions) throws Exception {
85          Assert.assertNotNull(url);
86          Configuration configuration = new Configuration(null, null, customActions);
87          SCXML scxml = SCXMLReader.read(url, configuration);
88          Assert.assertNotNull(scxml);
89          SCXML roundtrip = testModelSerializability(scxml);
90          return roundtrip;
91      }
92  
93      public static SCXML parse(final Reader scxmlReader, final List<CustomAction> customActions) throws Exception {
94          Assert.assertNotNull(scxmlReader);
95          Configuration configuration = new Configuration(null, null, customActions);
96          SCXML scxml = SCXMLReader.read(scxmlReader, configuration);
97          Assert.assertNotNull(scxml);
98          SCXML roundtrip = testModelSerializability(scxml);
99          return roundtrip;
100     }
101 
102     public static SCXMLExecutor getExecutor(final URL url) throws Exception {
103         return getExecutor(parse(url), null);
104     }
105 
106     public static SCXMLExecutor getExecutor(final String scxmlResource) throws Exception {
107         return getExecutor(parse(scxmlResource), null);
108     }
109 
110     public static SCXMLExecutor getExecutor(final SCXML scxml) throws Exception {
111         return getExecutor(scxml, null);
112     }
113 
114     public static SCXMLExecutor getExecutor(final URL url, final Evaluator evaluator) throws Exception {
115         return getExecutor(parse(url), evaluator);
116     }
117 
118     public static SCXMLExecutor getExecutor(final SCXML scxml, final Evaluator evaluator) throws Exception {
119         return getExecutor(scxml, evaluator, new SimpleDispatcher());
120     }
121 
122     public static SCXMLExecutor getExecutor(final SCXML scxml, final Evaluator evaluator, final EventDispatcher eventDispatcher) throws Exception {
123         Tracer trc = new Tracer();
124         SCXMLExecutor exec = new SCXMLExecutor(evaluator, eventDispatcher, trc);
125         exec.setStateMachine(scxml);
126         exec.addListener(scxml, trc);
127         return exec;
128     }
129 
130     public static TransitionTarget lookupTransitionTarget(SCXMLExecutor exec, String id) {
131         return exec.getStateMachine().getTargets().get(id);
132     }
133 
134     public static Context lookupContext(SCXMLExecutor exec, String id) {
135         TransitionTarget tt = lookupTransitionTarget(exec, id);
136         if (tt == null || !(tt instanceof EnterableState)) {
137             return null;
138         }
139         return exec.getSCInstance().lookupContext((EnterableState)tt);
140     }
141 
142     public static void assertState(SCXMLExecutor exec, String expectedStateId) throws Exception {
143         Set<EnterableState> currentStates = exec.getStatus().getStates();
144         Assert.assertEquals("Expected 1 simple (leaf) state with id '"
145             + expectedStateId + "' but found " + currentStates.size() + " states instead.",
146             1, currentStates.size());
147         Assert.assertEquals(expectedStateId, currentStates.iterator().
148             next().getId());
149     }
150 
151     public static Set<EnterableState> fireEvent(SCXMLExecutor exec, String name) throws Exception {
152         return fireEvent(exec, name, null);
153     }
154 
155     public static Set<EnterableState> fireEvent(SCXMLExecutor exec, String name, Object payload) throws Exception {
156         TriggerEvent[] evts = {new TriggerEvent(name, TriggerEvent.SIGNAL_EVENT, payload)};
157         exec.triggerEvents(evts);
158         return exec.getStatus().getStates();
159     }
160 
161     public static Set<EnterableState> fireEvent(SCXMLExecutor exec, TriggerEvent te) throws Exception {
162         exec.triggerEvent(te);
163         return exec.getStatus().getStates();
164     }
165 
166     public static Set<EnterableState> fireEvents(SCXMLExecutor exec, TriggerEvent[] evts) throws Exception {
167         exec.triggerEvents(evts);
168         return exec.getStatus().getStates();
169     }
170 
171     public static void assertPostTriggerState(SCXMLExecutor exec,
172             String triggerEventName, String expectedStateId) throws Exception {
173         assertPostTriggerState(exec, triggerEventName, null, expectedStateId);
174     }
175 
176     public static void assertPostTriggerState(SCXMLExecutor exec,
177             String triggerEventName, Object payload, String expectedStateId) throws Exception {
178         assertPostTriggerState(exec, new TriggerEvent(triggerEventName,
179                 TriggerEvent.SIGNAL_EVENT, payload), expectedStateId);
180     }
181 
182     public static void assertPostTriggerStates(SCXMLExecutor exec,
183             String triggerEventName, String[] expectedStateIds) throws Exception {
184         assertPostTriggerStates(exec, triggerEventName, null, expectedStateIds);
185     }
186 
187     public static void assertPostTriggerStates(SCXMLExecutor exec,
188             String triggerEventName, Object payload, String[] expectedStateIds) throws Exception {
189         assertPostTriggerStates(exec, new TriggerEvent(triggerEventName,
190                 TriggerEvent.SIGNAL_EVENT, payload), expectedStateIds);
191     }
192 
193     public static void assertPostTriggerState(SCXMLExecutor exec,
194             TriggerEvent triggerEvent, String expectedStateId) throws Exception {
195         Set<EnterableState> currentStates = fireEvent(exec, triggerEvent);
196         Assert.assertEquals("Expected 1 simple (leaf) state with id '"
197             + expectedStateId + "' on firing event " + triggerEvent
198             + " but found " + currentStates.size() + " states instead.",
199             1, currentStates.size());
200         Assert.assertEquals(expectedStateId, currentStates.iterator().
201             next().getId());
202     }
203 
204     public static void assertPostTriggerStates(SCXMLExecutor exec,
205             TriggerEvent triggerEvent, String[] expectedStateIds) throws Exception {
206         if (expectedStateIds == null || expectedStateIds.length == 0) {
207             Assert.fail("Must specify an array of one or more "
208                 + "expected state IDs");
209         }
210         Set<EnterableState> currentStates = fireEvent(exec, triggerEvent);
211         int n = expectedStateIds.length;
212         Assert.assertEquals("Expected " + n + " simple (leaf) state(s) "
213             + " on firing event " + triggerEvent + " but found "
214             + currentStates.size() + " states instead.",
215             n, currentStates.size());
216         List<String> expectedStateIdList = new ArrayList<String>(Arrays.asList(expectedStateIds));
217         for (TransitionTarget tt : currentStates) {
218             String stateId = tt.getId();
219             if(!expectedStateIdList.remove(stateId)) {
220                 Assert.fail("Expected state with id '" + stateId
221                     + "' in current states on firing event "
222                     + triggerEvent);
223             }
224         }
225         Assert.assertEquals("More states in current configuration than those"
226             + "specified in the expected state ids '" + expectedStateIds
227             + "'", 0, expectedStateIdList.size());
228     }
229 
230     public static SCXML testModelSerializability(final SCXML scxml) throws Exception {
231         File fileDir = new File(SERIALIZATION_DIR);
232         if (!fileDir.exists()) {
233             fileDir.mkdirs();
234         }
235         String filename = SERIALIZATION_FILE_PREFIX
236             + getSequenceNumber() + SERIALIZATION_FILE_SUFFIX;
237         SCXML roundtrip = null;
238         ObjectOutputStream out =
239             new ObjectOutputStream(new FileOutputStream(filename));
240         out.writeObject(scxml);
241         out.close();
242         ObjectInputStream in =
243             new ObjectInputStream(new FileInputStream(filename));
244         roundtrip = (SCXML) in.readObject();
245         in.close();
246         return roundtrip;
247     }
248 
249     public static SCXMLExecutor testInstanceSerializability(final SCXMLExecutor exec) throws Exception {
250         File fileDir = new File(SERIALIZATION_DIR);
251         if (!fileDir.exists()) {
252             fileDir.mkdirs();
253         }
254         String filename = SERIALIZATION_FILE_PREFIX
255             + getSequenceNumber() + SERIALIZATION_FILE_SUFFIX;
256         ObjectOutputStream out =
257             new ObjectOutputStream(new FileOutputStream(filename));
258         out.writeObject(exec.detachInstance());
259         out.close();
260         ObjectInputStream in =
261             new ObjectInputStream(new FileInputStream(filename));
262         exec.attachInstance((SCInstance) in.readObject());
263         in.close();
264         return exec;
265     }
266 
267     /**
268      * Parses a String containing XML source into a {@link Document}.
269      *
270      * @param xml The XML source as a String.
271      * @return The parsed {@link Document}.
272      */
273     public static Document stringToXMLDocument(final String xml) {
274         try {
275             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
276             dbf.setNamespaceAware(true);
277             return dbf.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
278         } catch (Exception e) {
279             throw new RuntimeException("Exception parsing String to Node:\n" + xml);
280         }
281     }
282 
283     /**
284      * Discourage instantiation.
285      */
286     private SCXMLTestHelper() {
287         super();
288     }
289 }