1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sf.jameleon;
20
21 import net.sf.jameleon.data.AbstractDataDrivableTag;
22 import net.sf.jameleon.exception.JameleonScriptException;
23 import net.sf.jameleon.result.*;
24 import net.sf.jameleon.util.JameleonUtility;
25 import net.sf.jameleon.util.StateStorer;
26 import org.apache.commons.jelly.JellyTagException;
27 import org.apache.commons.jelly.LocationAware;
28 import org.apache.commons.jelly.MissingAttributeException;
29 import org.apache.commons.jelly.XMLOutput;
30 import org.apache.log4j.Logger;
31
32 import java.io.IOException;
33 import java.util.List;
34 import java.util.Properties;
35 /***
36 * <p>A Session is the state of an application from one functional point to the next. In order
37 * to allow for independent functional points, a handle on the application's state
38 * must be kept between functional points. This handle is implemented differently for each
39 * application interface technology. Some examples of interfaces might be a GUI application
40 * implemented in Swing or .NET, an HTTP application or even a SQL inteface to the database.
41 * </p>
42 * <p>A SessionTag is required for every interface, even those that don't require a handle
43 * on the application to be shared across the many functional points. There should still be
44 * a generic way to start and stop the application. A SessionTag is also a means to use only
45 * variables defined in Applications.properties that are pertinent to a particular application
46 * and it helps separate the autogenerated docs when accessing several applications
47 * in a test.
48 * </p>
49 * <p>Implementing an interface-specific SessionTag includes implementing the following methods:
50 * <ul>
51 * <li>{@link #setUpSession()} - This method is called after @see #init() and should be used to create
52 * all plug-in related resources and configure any plug-in/session specific
53 * settings.
54 * <li>{@link #tearDownSession()} - This method is called right after all children tags have been executed
55 * and should clean up any resources created by the session tag</li>
56 * <li>{@link #startApplication()} - This method is used to start the application and is called only when
57 * <code>beginSession</code> is set to <code>true</code>. If this method
58 * doesn't make sense for the particular plug-in, then simply don't
59 * implement it.</li>
60 * </ul>
61 * </p>
62 * <p>If the application type being tested supplies some kind of handle, then the SessionTag is the appropriate place
63 * to keep it. Create an instance variable representing the application's handle and provide a public get and set
64 * method for it. The abstract function tag will then get an instance of this SessionTag and get the handle
65 * off the application.
66 * </p>
67 * <p>
68 * This base class is interface agnostic and implements all Jameleon-specific behavior. The
69 * following is a list of supported attributes supported by this class:
70 * <ul>
71 * <li>sessionResults - This is a generic result. It keeps track of a lot of stuff, however. {@link net.sf.jameleon.result.SessionResult}.</li>
72 * <li>application - The name of the application being tested. This used to grab information about the application transparently for the
73 * funtional points. Can be set via the <code>application</code> attribute in the appropriate session tag.</li>
74 * <li>organization - The name of the organization this Session is running tests against. This is not required and is also used to transparently
75 * get information regarding the URL to start on and any other attributes which might be specific to an application and or an enviroment.
76 * <code>organization</code> can also be set with the <code>organization</code> attribute in either the test case or in the
77 * appropriate session tag. This would only need to be set in the session if the organization is set in the the test case
78 * the application being tested in this session is different from that in the test case.</li>
79 * <li>log - An instance of the @see java.util.loggin.Logger class. This is the means that Jameleon uses for it's logging..
80 * </ul>
81 * </p>
82 */
83 public abstract class SessionTag extends JameleonTagSupport implements Storable,
84 FunctionResultRecordable,
85 DataDrivableResultRecordable{
86
87 protected SessionResult sessionResults;
88 /***
89 * The name of the application being run according to the *-TestCaseTag.properties file
90 * @jameleon.attribute
91 */
92 protected String application;
93 /***
94 * The organization (affiliate or company name) this application will be tested against.
95 * @jameleon.attribute
96 */
97 protected String organization;
98 /***
99 * @jameleon.attribute
100 */
101 protected boolean postcondition;
102 /***
103 * Sets the tag to delay x milliseconds before anything else is executed.
104 * @jameleon.attribute default="0"
105 */
106 protected long sessionDelay;
107 protected static Logger log = Logger.getLogger(SessionTag.class.getName());
108 protected Properties props;
109 protected TestCaseTag tc;
110 protected AbstractDataDrivableTag addt;
111
112 private String tcOrganization;
113
114 /***
115 * Starts the applicattion and gets it to the state defined in the $testEnviroment-Applications.properties.
116 * DEFAULTS to <code>false</code>
117 * @jameleon.attribute
118 */
119 protected boolean beginSession = false;
120
121 /***
122 * Default constructor. Currently it does nothing.
123 */
124 public SessionTag(){
125 super();
126 application = new String();
127 }
128
129 /***
130 * @return the organization (affiliate or company name) this application will be tested against.
131 * This would used for when there is one application for many different datasources
132 * like a shopping being installed against several different customers. This is also
133 * important because the URLs change between organizations as well.
134 */
135 public String getOrganization(){
136 return organization;
137 }
138
139 public void setOrganization(String organization){
140 this.organization = organization;
141 }
142
143 /***
144 * The name of the application being run according to the *-TestCaseTag.properties file
145 * @return the name of the application being run under this session.
146 */
147 public String getApplication(){
148 return this.application;
149 }
150
151 public void setSessionDelay(long sessionDelay){
152 this.sessionDelay = sessionDelay;
153 }
154
155 /***
156 * A TagSupport specific method. This method calls any or all sub element tags ( like function point tags ).
157 */
158 public void doTag(XMLOutput out) throws MissingAttributeException, JellyTagException{
159 init();
160 traceMsg("Beginning Session: \""+ getFunctionalPoint().getDefaultTagName()+"\"");
161 testForUnsupportedAttributesCaught();
162 broker.transferAttributes(context);
163 broker.validate(context);
164 setUpSession();
165 long startTime = System.currentTimeMillis();
166 try {
167 if (tc.isExecuteTestCase()) {
168 delaySession();
169 if (beginSession &&
170 (!addt.getFailedOnCurrentRow() || postcondition)) {
171 traceMsg("Begin: starting application");
172 startApplication();
173 traceMsg("End: starting application");
174 }
175 }
176 } catch (RuntimeException e) {
177 sessionResults.setError(e);
178 log.debug(JameleonUtility.getStack(e));
179 addt.setFailedOnCurrentRow(true);
180 }
181 try {
182 invokeBody(out);
183 } catch (ThreadDeath td){
184 throw td;
185 } catch (Throwable t) {
186 LocationAware la = null;
187 Throwable err = t;
188 if (t.getCause() != null && t.getCause() instanceof LocationAware) {
189 err = t.getCause();
190 la = (LocationAware)err;
191 }else if (t instanceof LocationAware) {
192 la = (LocationAware)t;
193 }
194 if (la != null) {
195 la.setColumnNumber(getColumnNumber());
196 la.setLineNumber(getLineNumber());
197 la.setFileName(getFileName());
198 JameleonScriptException jse = new JameleonScriptException(err.getMessage(), la);
199 sessionResults.setError(jse);
200
201 }else{
202 sessionResults.setError(err);
203 }
204 } finally {
205 sessionResults.setExecutionTime(System.currentTimeMillis()-startTime);
206
207
208 removeChildlessResult();
209 log.debug(sessionResults);
210 if (tcOrganization != null) {
211 tc.setOrganization(tcOrganization);
212 context.setVariable("organization", tcOrganization);
213 }
214 tearDown();
215 }
216 }
217
218 protected void tearDown(){
219 props = new Properties();
220 traceMsg("Ending Session: \""+getFunctionalPoint().getDefaultTagName()+"\"");
221 traceMsg("\n");
222 tearDownSession();
223 sessionResults.setTag(fp.cloneFP());
224 resetFunctionalPoint();
225 }
226
227 /***
228 * Used for the trace functionality. Only sends info to the log if trace is enabled.
229 * @deprecated - use traceMsg instead.
230 */
231 protected void trace(String msg){
232 traceMsg(msg);
233 }
234
235 /***
236 * Used for the trace functionality. Only sends info to the log if trace is enabled.
237 */
238 protected void traceMsg(String msg){
239 if (tc != null && tc.getTrace()) {
240 System.out.println(msg+"\n");
241 }else{
242 log.debug(msg);
243 }
244 }
245
246
247 /***
248 * <p>
249 * Stores the current state of the object to a given <code>File</code>. The default
250 * implementation of this method does nothing. Override this method to implement
251 * plug-in specific behavior. Some examples might be:</p>
252 * <ul>
253 * <li>Saving the HTML from an HTTP plug-in during a state change or error</li>
254 * <li>Taking a screen shot for a GUI application during a state change event</li>
255 * </ul>
256 * <p>
257 * A listener is already registered for each function tag. All that is required is
258 * implementing this method.</p>
259 * @param filename the name of the if the file to store the contents to.
260 * @param event The {@link net.sf.jameleon.util.StateStorer event} that occured (error, state change ...).
261 * @throws IOException If the state of the object could not be stored in File <code>f</code>.
262 */
263 public void store(String filename, int event) throws IOException{
264 }
265
266 /***
267 * Gets the filename to store the state of the application to.
268 * The default implementation is to simply use timestamps.
269 * If this is not the desired behavior, override this method.
270 * @param event - the StateStorer Event
271 * @return the appropriate filename which starts with ERROR- if the StateStorer Event was an Error
272 */
273 public String getStoreToFileName(int event){
274 String filename = System.currentTimeMillis() +"";
275 if ( event == StateStorer.ON_ERROR_EVENT ) {
276 filename = "ERROR-"+filename;
277 }
278 return filename;
279 }
280
281
282 /***
283 * Called when the session tag is closed. This should clean up
284 * all plug-in specific resources created by this session.
285 */
286 protected void tearDownSession(){}
287
288 protected void findParentTestCase() {
289 tc = (TestCaseTag)findAncestorWithClass(TestCaseTag.class);
290 }
291
292 /***
293 * Grabs information about the application and the organization from the test case.
294 * Adds the test case results to the session results so that overall statistics will be kept in the
295 * test case results.
296 */
297 protected void init() throws MissingAttributeException{
298 findParentTestCase();
299 addt = (AbstractDataDrivableTag)findAncestorWithClass(AbstractDataDrivableTag.class);
300 if (organization != null && organization.trim().length() > 0) {
301 tcOrganization = tc.getOrganization();
302 context.setVariable("organization", organization);
303 tc.setOrganization(organization);
304 tc.validateAttributes();
305 } else {
306 organization = tc.getOrganization();
307 }
308
309 Object obj = findAncestorWithClass(PostconditionTag.class);
310 if (obj != null) {
311 postcondition = true;
312 }
313
314 sessionResults = new SessionResult(fp);
315 sessionResults.copyLocationAwareProperties(this);
316 obj = findAncestorWithClass(SessionResultRecordable.class);
317 if (obj != null) {
318 ((SessionResultRecordable)obj).recordSessionResult(sessionResults);
319 }
320 context.setVariable("session_application", application);
321 props = tc.getPropertiesForApplication(application);
322 }
323
324 /***
325 * @return The results of this session. This should contain all environment variables used as well
326 * as all of the asserts.
327 */
328 public SessionResult getSessionResult(){
329 return sessionResults;
330 }
331
332 /***
333 * Is called before anything else specific to the interface is called. This method should
334 * create all resources required for this session to begin. The default behavior is to
335 * do nothing.
336 */
337 public void setUpSession(){}
338
339 /***
340 * Used for the plug-in to implement if something special is required during the session setup.
341 * The properties from the CSV and testEnvironment.properties are setup before this method is called.
342 * The default implementation does nothing.
343 */
344 public void startApplication(){}
345
346 protected void delaySession(){
347 if (sessionDelay > 0) {
348 synchronized (this){
349 try {
350 this.wait(sessionDelay);
351 } catch (InterruptedException e) {
352 e.printStackTrace();
353 }
354 }
355 }
356 }
357
358
359 /***
360 * Removes the current result from its parent if it has no children, meaning the tags weren't actually run
361 */
362 protected void removeChildlessResult(){
363 if (sessionResults.passed() &&
364 ( sessionResults.getChildrenResults() == null ||
365 sessionResults.getChildrenResults().size() == 0) ) {
366
367 if (sessionResults.getParentResults() != null) {
368 List results = sessionResults.getParentResults().getChildrenResults();
369 if (results != null) {
370 int index = results.lastIndexOf(sessionResults);
371 results.remove(index);
372 }
373 }
374 }
375 }
376
377
378
379
380
381 protected void recordResult(JameleonTestResult result){
382 if (sessionResults != null) {
383 sessionResults.addChildResult(result);
384 result.setParentResults(sessionResults);
385 }
386 }
387 /***
388 * Records a FunctionResult to the tag's results and sets the FunctionResult's parent
389 * result to itself
390 * @param result
391 */
392 public void recordFunctionResult(FunctionResult result) {
393 recordResult(result);
394 }
395 /***
396 * Records a DataDrivableResultContainer to the tag's results
397 * @param result
398 */
399 public void recordDataDrivableResult(DataDrivableResultContainer result){
400 recordResult(result);
401 }
402 }