View Javadoc

1   /*
2       Jameleon - An automation testing tool..
3       Copyright (C) 2003-2006 Christian W. Hargraves (engrean@hotmail.com)
4       
5       This library is free software; you can redistribute it and/or
6       modify it under the terms of the GNU Lesser General Public
7       License as published by the Free Software Foundation; either
8       version 2.1 of the License, or (at your option) any later version.
9   
10      This library is distributed in the hope that it will be useful,
11      but WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13      Lesser General Public License for more details.
14  
15      You should have received a copy of the GNU Lesser General Public
16      License along with this library; if not, write to the Free Software
17      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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                 //throw jse;
201             }else{
202                 sessionResults.setError(err);
203             }
204         } finally {
205             sessionResults.setExecutionTime(System.currentTimeMillis()-startTime);
206             // A hack to show the variables created during execution of a function point( like the variable mapping feature )
207             //sessionResults.getParams().putAll(context.getVariables());
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     //Storable Methods
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     //End Storable Methods
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     //                      FunctionResultRecordable implementation methods                     //
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 }