View Javadoc

1   /*
2       Jameleon - An automation testing tool..
3       Copyright (C) 2003-2007 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.data;
20  
21  import net.sf.jameleon.JameleonTagSupport;
22  import net.sf.jameleon.SessionTag;
23  import net.sf.jameleon.TestCaseTag;
24  import net.sf.jameleon.event.BreakPoint;
25  import net.sf.jameleon.exception.JameleonScriptException;
26  import net.sf.jameleon.result.*;
27  import net.sf.jameleon.util.StateStorer;
28  import org.apache.commons.jelly.JellyTagException;
29  import org.apache.commons.jelly.LocationAware;
30  import org.apache.commons.jelly.MissingAttributeException;
31  import org.apache.commons.jelly.XMLOutput;
32  import org.apache.log4j.Logger;
33  
34  import java.io.File;
35  import java.io.FileNotFoundException;
36  import java.io.IOException;
37  import java.util.*;
38  
39  /***
40   * This is a basic implementation of DataDrivable. This is data source
41   * independent as possible for now.
42   */
43  public abstract class AbstractDataDrivableTag 
44      extends 
45          JameleonTagSupport 
46      implements 
47          DataDrivable, 
48          BreakPoint,
49          DataDrivableResultRecordable,
50          FunctionResultRecordable,
51          SessionResultRecordable
52          {
53      
54      protected TestCaseTag tct;
55      protected AbstractDataDrivableTag addt;
56      protected DataExecuter executer = new DataExecuter(getDataDriver());
57      protected XMLOutput xmlOut;
58      protected Map rowData = new HashMap();
59      protected Map vars;
60  
61      protected DataDrivableResultContainer resultContainer;
62      protected DataDrivableRowResult dataDrivableRowResult;
63      protected boolean countRow;
64      protected StateStorer stateStorer = StateStorer.getInstance();
65      protected File previousStateDir;
66      protected int numOfRowFailures;
67      protected boolean failedOnCurrentRow;
68      protected boolean stopTestExecutionOnFailure = true;
69      protected boolean parentFailed;
70      protected boolean breakPoint;
71      protected DataDrivableResultRecordable resultRecorder;
72      
73      protected final Logger log = getLogger();
74  
75      /***
76       * Gets the logger used for this tag
77       * @return the logger used for this tag.
78       */
79      protected abstract Logger getLogger();
80      /***
81       * Gets the DataDriver used for this tag.
82       * @return the DataDriver used for this tag.
83       */
84      protected abstract DataDriver getDataDriver();
85      /***
86       * Sets up the DataDriver by calling any implementation-dependent
87       * methods.
88       */
89      protected abstract void setupDataDriver();
90      /***
91       * Gets the trace message when the execution is beginning and ending.
92       * The message displayed will already start with BEGIN: or END:
93       * @return the trace message when the execution is just beginning and ending.
94       */
95      protected abstract String getTagTraceMsg();
96      /***
97       * Describe the tag when error messages occur.
98       * The most appropriate message might be the tag name itself.
99       * @return A brief description of the tag or the tag name itself.
100      */
101     public abstract String getTagDescription();
102     /***
103      * Gets an error message to be displayed when a error occurs due to the DataDriver.
104      * @return an error message to be displayed when a error occurs due to the DataDriver.
105      */
106     protected abstract String getDataExceptionMessage();
107     /***
108      * Calculates the location of the state to be stored for any tags under this tag.
109      * The result should be a relative path that simply has the row number in it along
110      * with some unique indentifier for this tag like the handle name or something.
111      * @return the location of the state to be stored for any tags under this tag minus the baseDir calculation stuff.
112      */
113     protected abstract String getNewStateStoreLocation(int rowNum);
114 
115     /***
116      * Gets the TestCaseTag for this tag. The default implementation searches
117      * for it as an ancestor and throws a ClassCastException if it can't find it.
118      * @return the TestCaseTag this tag is nested in.
119      * @throws ClassCastException if the parent TestCaseTag can not be found.
120      */
121     protected TestCaseTag getTestCaseTag(){
122         Object obj =  findAncestorWithClass(TestCaseTag.class);
123         if (obj instanceof TestCaseTag) {
124             return (TestCaseTag)obj;
125         }else{
126             throw new ClassCastException("Can only execute the "+getTagDescription()+" inside a testcase tag! "+getClass().getName());
127         }
128     }
129 
130     /***
131      * Gets whether each row in the data source should be considered a separate test case or not.
132      * @return true if each row in the data source should be considered a separate test case.
133      */
134     public boolean isCountRow(){
135         return countRow;
136     }
137 
138     /***
139      * Sets the DataDrivable tag to increment the number of times the test case was executed
140      * for each row executed.
141      * @param countRow - Set to true to increment the test case results for every row executed
142      * @jameleon.attribute default="false"
143      */
144     public void setCountRow(boolean countRow){
145         this.countRow = countRow;
146     }
147 
148     public void setFailedOnCurrentRow(boolean failedOnCurrentRow){
149         if (stopTestExecutionOnFailure) {
150             this.failedOnCurrentRow = failedOnCurrentRow;
151             if (addt != null && addt != this) {
152                 addt.setFailedOnCurrentRow(failedOnCurrentRow);
153             }
154         }
155     }
156 
157     public boolean getFailedOnCurrentRow(){
158         return parentFailed || failedOnCurrentRow ;
159     }
160 
161     /***
162      * Removes the keys from the context and the variable set in addVariablesToRowData()
163      * @param keys - A Set of variable names to clean up.
164      */
165     public void destroyVariables(Set keys){
166         if (tct != this && tct != null) {
167             tct.destroyVariables(keys);
168         }
169         Iterator keysIt = keys.iterator();
170         String key = null;
171         while (keysIt.hasNext()) {
172             key = (String)keysIt.next();
173             context.removeVariable(key);
174             rowData.remove(key);
175         }
176     }
177 
178     /***
179      * Used to keep track of the variables and their original values.
180      * This is mostly used for variable substitution.
181      * @param rowData - A map of key-value pairs.
182      */
183     public void addVariablesToRowData(Map rowData){
184         this.vars = rowData;
185         mapKeys(vars);
186         setVariablesInContext(vars);
187         tct.addVariablesToRowData(vars);
188         Object obj =  findAncestorWithClass(SessionTag.class);
189         if (obj instanceof SessionTag) {
190             tct.substituteKeyValues();
191             SessionTag st = (SessionTag)obj;
192             String app = st.getApplication();
193             if (app != null && app.trim().length() > 0) {
194                 tct.getPropertiesForApplication(app);
195             }
196         }
197     }
198 
199     /***
200      * This method is used to set up anything the tag may need and gets
201      * called after all set methods have been called.
202      */
203     public void setUpDataDrivable(){
204         tct = getTestCaseTag();
205         previousStateDir = stateStorer.getStoreDir();
206         if (tct != this && stopTestExecutionOnFailure) {
207             parentFailed = tct.getFailedOnCurrentRow();
208         }
209         setupDataDriver();
210     }
211 
212     /***
213      * Maps the keys to the keys returned from {@link #getKeyMapping()}
214      * This method is called from executeDrivableRow(HashMap vars, int rowNum)
215      * and is used to allow subclasses to map the variable names defined as
216      * the keys in the vars Map to new keys which will then in turn be stored in the context.
217      * @param vars - the original map read from the DataDriver
218      */
219     public void mapKeys(Map vars){
220         Map keys = getKeyMapping();
221         if (keys != null) {
222             Iterator it = keys.keySet().iterator();
223             String oldKey,newKey;
224             while (it.hasNext()) {
225                 oldKey = (String) it.next();
226                 newKey = (String) keys.get(oldKey);
227                 vars.put(newKey, vars.get(oldKey));
228                 vars.remove(oldKey);
229             }
230         }
231     }
232 
233     /***
234      * Gets the new names of the desired context names. 
235      * The map should contain a key-value pair where the key is
236      * the original key and the value is the new key desired.
237      * The default implementation returns null.
238      * @return a key-value pair representing the new variable names.
239      */
240     protected Map getKeyMapping(){
241         return null;
242     }
243 
244     /***
245      * A DataDrivable implementation method that gets called once for every row in the data source.
246      */
247     public void executeDrivableRow(int rowNum){
248         traceMsg("BEGIN executing row number "+rowNum);
249         try{
250             //The first result has already been added.
251             if (rowNum > 1){
252                 dataDrivableRowResult = createNewResult();
253                 recordThisResult();
254             }
255             dataDrivableRowResult.setRowData(vars);
256             dataDrivableRowResult.setRowNum(rowNum);
257             stateStorer.setStoreDir(new File(stateStorer.getStoreDir(), getNewStateStoreLocation(rowNum)));
258             try {
259                 invokeBody(xmlOut);
260             } catch (ThreadDeath td){
261                 throw td;
262             } catch (Throwable t) {
263                 LocationAware la = null;
264                 Throwable err = t;
265                 if (t.getCause() != null && t.getCause() instanceof LocationAware) {
266                     err = t.getCause();
267                     la = (LocationAware)err;
268                 }else if (t instanceof LocationAware) {
269                     la = (LocationAware)t;
270                 }
271                 if (la != null) {
272                     la.setColumnNumber(getColumnNumber());
273                     la.setLineNumber(getLineNumber());
274                     la.setFileName(getFileName());
275                     JameleonScriptException jse = new JameleonScriptException(err.getMessage(), la);
276                     dataDrivableRowResult.setError(jse);
277                     throw jse;
278                 }else{
279                     dataDrivableRowResult.setError(err);
280                 }
281             } finally {
282                 stateStorer.setStoreDir(previousStateDir);
283                 if (getFailedOnCurrentRow() && (isCountRow() || numOfRowFailures < 1 )){
284                     numOfRowFailures++;
285                 }
286             }
287         }finally{
288             removeChildlessResult(dataDrivableRowResult);
289             failedOnCurrentRow = false;
290             traceMsg("END executing row number "+rowNum);
291         }
292     }
293 
294     public DataDrivableResultContainer getResultContainer(){
295         return resultContainer;
296     }
297 
298     /***
299      * To continue on a normal execution path even though an error occurs, set this to false
300      * @jameleon.attribute
301      */
302     public void setStopTestExecutionOnFailure(boolean stopTestExecutionOnFailure){
303         this.stopTestExecutionOnFailure = stopTestExecutionOnFailure;
304     }
305 
306     /***
307      * Places the given variables in the context
308      */
309     public void setVariablesInContext(Map vars){
310         Iterator it = vars.keySet().iterator();
311         String key;
312         while (it.hasNext()) {
313             key = (String) it.next();
314             context.setVariable(key, vars.get(key));
315         }
316     }
317 
318     /***
319      * Traces the key value pairs to screen for debugging purposes
320      * This should get called after the key substition is done
321      */
322     public String getTraceKeyValuePairs(Set keys, Map rowData){
323         Iterator it = keys.iterator();
324         String key;
325         String traceMsg = "key=>value: ";
326         while (it.hasNext()) {
327             key = (String) it.next();
328             traceMsg += "'"+key+"'=>'"+rowData.get(key)+"' ";
329         }
330         return traceMsg;
331     }
332 
333     /***
334      * Used for the trace functionality. Only sends info to the log if trace is enabled.
335      */
336     protected void traceMsg(String msg){
337         if (tct != null && tct.getTrace()) {
338             System.out.println("\n"+msg+"\n");
339         }else{
340             log.debug(msg);        	
341         }
342     }
343 
344     public void init() throws MissingAttributeException{
345         addt = (AbstractDataDrivableTag)findAncestorWithClass(AbstractDataDrivableTag.class);
346         if (addt == null) {
347             addt = this;
348         }
349         if (isCountRow()) {
350             resultContainer = new CountableDataDrivableResultContainer(fp.cloneFP());
351         }else{
352             resultContainer = new DataDrivableResultContainer(fp.cloneFP());
353         }
354         dataDrivableRowResult = createNewResult();
355         Object obj = findAncestorWithClass(DataDrivableResultRecordable.class);
356         if (obj != null) {
357             resultRecorder = (DataDrivableResultRecordable)obj;
358             resultContainer.copyLocationAwareProperties(this);
359             resultRecorder.recordDataDrivableResult(resultContainer);
360         }
361         recordThisResult();
362     }
363 
364     protected void recordThisResult(){
365         dataDrivableRowResult.copyLocationAwareProperties(this);
366         if (resultRecorder != null) {
367             resultContainer.addChildResult(dataDrivableRowResult);
368             dataDrivableRowResult.setParentResults(resultContainer);
369         }
370     }
371 
372     /***
373      * This method executes the tags inside the csv tag one time for every row in the CSV file used.
374      */
375     public void doTag(XMLOutput out) throws MissingAttributeException, JellyTagException{
376         xmlOut = out;
377         setUpDataDrivable();
378         init();
379         testForUnsupportedAttributesCaught();
380         broker.transferAttributes(context);
381         broker.validate(context);
382         try{
383             traceMsg("BEGIN: "+getTagTraceMsg());
384             executer.executeData(this, false);
385             traceMsg("END: "+getTagTraceMsg());
386         }catch(FileNotFoundException fnfe){
387             tct.setFailedOnDataDriver(true);
388             setResultError(fnfe);
389         }catch(IOException ioe){
390             setResultError(new JameleonScriptException(getDataExceptionMessage(), ioe));
391         }catch(ThreadDeath td){
392             throw td;
393         }catch(Throwable t){
394             setResultError(new JameleonScriptException(t));
395         }finally{
396             dataDrivableRowResult.setTag(fp.cloneFP());
397             resetFunctionalPoint();
398         }
399     }
400 
401     /***
402      * Removes the current rowResult from its parent if it has no children, meaning the tags weren't actually run
403      * @param rowResult - The rowResult to remove
404      */
405     protected void removeChildlessResult(DataDrivableRowResult rowResult){
406         if (rowResult.passed() &&
407             ( rowResult.getChildrenResults() == null ||
408               rowResult.getChildrenResults().size() == 0 ) )  {
409             if (rowResult.getParentResults() != null) {
410                 List results = rowResult.getParentResults().getChildrenResults();
411                 if (results != null) {
412                     int index = results.lastIndexOf(rowResult);
413                     results.remove(index);
414                 }
415             }
416         }
417     }
418 
419     protected void setResultError(Exception e){
420     	dataDrivableRowResult.setError(e);
421         resultContainer.setError(e);
422     }
423     
424     protected DataDrivableRowResult createNewResult(){
425     	DataDrivableRowResult ddr = null;
426     	if (isCountRow()){
427     		ddr = new CountableDataDrivableRowResult(fp.cloneFP());
428     	}else{
429     		ddr = new DataDrivableRowResult(fp.cloneFP());
430     	}
431     	return ddr;
432     }
433 
434 
435     public Map getRowData(){
436         return rowData;
437     }
438     
439     public DataDrivableRowResult getDataDrivableRowResult(){
440     	return dataDrivableRowResult;
441     }
442     
443     /////////////////////////////////////////////////////////
444     //          ResultRecordable Methods       //
445     /////////////////////////////////////////////////////////
446 
447     /***
448      * Records a child result. Used as a helper method for the ResultRecordable
449      * implementation methods.
450      * @param result - the result to record
451      */
452     protected void recordResult(JameleonTestResult result){
453         if (dataDrivableRowResult != null) {
454             dataDrivableRowResult.addChildResult(result);
455             result.setParentResults(dataDrivableRowResult);
456         }
457     }
458     /***
459      * Records a DataDrivableRowResult to the tag's results
460      * @param result
461      */
462     public void recordDataDrivableResult(DataDrivableResultContainer result){
463         recordResult(result);
464     }
465 
466     /***
467      * Records a FunctionResult to the tag's results and sets the FunctionResult's parent
468      * result to itself
469      * @param result
470      */
471     public void recordFunctionResult(FunctionResult result){
472         recordResult(result);
473     }
474 
475     /***
476      * Removes a FunctionResult from the list of recorded results
477      * @param result - the result to remove
478      */
479     public void recordSessionResult(SessionResult result){
480         recordResult(result);
481     }
482 /////////////////////////////////////////////////////////////
483 ////                 BreakPoint Methods                //////
484 /////////////////////////////////////////////////////////////
485     /***
486      * Sets a pause point to this class.
487      * @param breakPoint - Set to false to enable debug mode.
488      * @jameleon.attribute
489      */
490     public void setBreakPoint(boolean breakPoint){
491         this.breakPoint = breakPoint;
492     }
493 
494     /***
495      * Tells if this class is supposed to pause
496      * @return true if class should pause and wait for user interaction.
497      */
498     public boolean isBreakPoint(){
499         return breakPoint;
500     }
501 
502 }