1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
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
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 }