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.bean.TestCase;
22 import net.sf.jameleon.data.DataDrivable;
23 import net.sf.jameleon.event.TestCaseEventHandler;
24 import net.sf.jameleon.exception.JameleonScriptException;
25 import net.sf.jameleon.result.*;
26 import net.sf.jameleon.util.*;
27 import org.apache.commons.beanutils.BeanUtils;
28 import org.apache.commons.jelly.*;
29 import org.apache.commons.jelly.expression.CompositeExpression;
30 import org.apache.commons.jelly.expression.Expression;
31 import org.apache.commons.jelly.expression.jexl.JexlExpressionFactory;
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.io.InputStream;
38 import java.util.*;
39
40 /***
41 * Every test case script must have at least one testcase tag containing all other Jameleon tags.
42 * <p>
43 * Some of this tags attribute may affect every nested Jameleon tag.
44 * Many of the attributes in this tag can be set globally via a jameleon.conf file.
45 * </p>
46 * <p>
47 * The order of setting variables in the context follows:
48 * <ol>
49 * <li> Load the CSV file variables and put them in the context.</li>
50 * <li> Load the $testEnvironment-Applications.properties and then Applications.properties and
51 * only set the variables that aren't set in the previous files. In other words, if there
52 * are variables that are going to be the same ( like the page title ) across multiple test
53 * cases, then first variable set wins.</li>
54 * <li> Execute the function tag and set the attributes in the context. If you want key/values in
55 * the CSV and properties files to override the script attribute, then the function point
56 * author uses the setDefaultVariableValue() method in the set method for that attribute.</li>
57 * <li> If the function point is using a map-variable, then override all settings to set the
58 * variable to the mapFrom variable name.</li>
59 * </ol>
60 * </p>
61 * @jameleon.function name="testcase"
62 * @jameleon.function name="test-case"
63 */
64 public class TestCaseTag extends AbstractCsvTag {
65
66 protected long maxExecutionTime = 0;
67 protected String assertGreaterThanLevel;
68 protected String assertLessThanLevel;
69 protected String assertLevel;
70 protected String assertLevels;
71 protected String testEnvironment = "";
72 protected String organization = "";
73 protected String csvValueSeparator;
74 protected String bugTrackerUrl;
75 protected String genTestCaseDocsEncoding = JameleonDefaultValues.FILE_CHARSET;
76 protected String propsName;
77 protected boolean useCSV;
78 protected boolean trace;
79 protected boolean genTestCaseDocs = true;
80 protected boolean executeTestCase = true;
81
82 protected String testCaseResultDataRowTemplate = JameleonDefaultValues.TEST_CASE_RESULT_DATA_ROW_TEMPLATE;
83 protected String testCaseResultSessionTemplate = JameleonDefaultValues.TEST_CASE_RESULT_SESSION_TEMPLATE;
84 protected String testCaseResultFunctionTemplate = JameleonDefaultValues.TEST_CASE_RESULT_FUNCTION_TEMPLATE;
85 protected String testCaseMainPageTemplate = JameleonDefaultValues.TEST_CASE_RESULT_MAIN_PAGE_TEMPLATE;
86 protected String testCaseSummaryTemplate = JameleonDefaultValues.TEST_CASE_SUMMARY_TEMPLATE;
87 protected String testCaseResultSummaryTemplate = JameleonDefaultValues.TEST_CASE_RESULT_SUMMARY_TEMPLATE;
88 protected String testCaseResultTemplate = JameleonDefaultValues.TEST_CASE_RESULT_TEMPLATE;
89
90 protected TestCaseEventHandler eventHandler = TestCaseEventHandler.getInstance();
91 protected long startTime;
92 protected File resultsFile;
93 protected CountableDataDrivableResultContainer rowResultContainer;
94
95 /***
96 * DEFAULT - true.
97 * If set to false, then don't error and don't even log the test case. If a file is found, then go ahead and log test case results
98 */
99 protected boolean failOnCSVFileNotFound = true;
100 /***
101 * Used to flag if a CSV file is not found. This is used only when failOnCSVFileNotFound is set to false
102 */
103 protected boolean failedOnDataDriver = false;
104 /***
105 * The test case results which are a complete set of results for every tag executed.
106 */
107 protected TestCaseResult results;
108
109
110 protected DataDrivableRowResult ddRowResult;
111
112
113 public DataDrivableRowResult getDdResult() {
114 return ddRowResult;
115 }
116
117 protected ArrayList keysSet = new ArrayList();
118 protected TestCase testCase = new TestCase();
119
120 /***
121 * Only store the displayed screen being tested to a file on an error.
122 */
123 protected boolean storeDisplayOnError = true;
124 /***
125 * Store all displayed screens to a file..
126 */
127 protected boolean storeEveryDisplay = false;
128 /***
129 * The baseDir where everything else is based
130 */
131 protected File baseDir = JameleonDefaultValues.BASE_DIR;
132 /***
133 * The directory name to store the results to. Defaults to ./jameleon_test_results
134 */
135 protected File resultsDir = new File(baseDir, JameleonDefaultValues.RESULTS_DIR);
136 /***
137 * The timestamped directory to store the results to.
138 */
139 protected File timestampedResultsDir;
140 /***
141 * The name of the Jameleon configuration file. (default is defined in {@link net.sf.jameleon.util.Configurator})
142 */
143 protected String jameleonConfigName = Configurator.DEFAULT_CONFIG_NAME;
144 /***
145 * Enable/disable validity checking for SSL certificates. Default is true (enabled).
146 * Set to false to prevent exceptions from being thrown for invalid SSL certs.
147 */
148 protected boolean enableSslCertCheck = true;
149
150 protected final static String APPLICATIONS_PROPERTIES = "Applications";
151 protected final static int DEFAULT_ROW = 0;
152 protected final static String DEFAULT_VALUE_SEPARATOR = ",";
153
154 /***
155 * Gets the logger used for this tag
156 * @return the logger used for this tag.
157 */
158 protected Logger getLogger(){
159 return Logger.getLogger(TestCaseTag.class.getName());
160 }
161
162 /***
163 * Gets the trace message when the execution is beginning and ending.
164 * The message displayed will already start with BEGIN: or END:
165 * @return the trace message when the execution is just beginning and ending.
166 */
167 protected String getTagTraceMsg(){
168 return "parsing " + getCsvFile();
169 }
170
171 /***
172 * Describe the tag when error messages occur.
173 * The most appropriate message might be the tag name itself.
174 * @return A brief description of the tag or the tag name itself.
175 */
176 public String getTagDescription(){
177 return "testcase tag";
178 }
179
180 protected TestCaseTag getTestCaseTag(){
181 return this;
182 }
183
184 /***
185 * Used to add key/values to a local context for multiple variable
186 * substitution. Since a test case can have multiple sessions and sessions
187 * are application specific, then variables with ${varName} in them can
188 * be different values depending on the application settings in the Applications.properties.
189 */
190 public void addVariablesToRowData(Map vars){
191 rowData.putAll(vars);
192 substituteKeyValues();
193 traceMsg(getTraceKeyValuePairs(vars.keySet(), context.getVariables()));
194 }
195
196 /***
197 * Gets the session template to be used to generate the session result.
198 * This is searched for in the classpath.
199 * @return the session template to be used to generate the test case docs.
200 */
201 public String getTestCaseResultSessionTemplate() {
202 return testCaseResultSessionTemplate;
203 }
204
205 /***
206 * Sets the session template to be used to generate the session result.
207 * This is searched for in the classpath.
208 * @param testCaseResultSessionTemplate the session template to be used to generate the test case docs.
209 * @jameleon.attribute
210 */
211 public void setTestCaseResultSessionTemplate(String testCaseResultSessionTemplate) {
212 this.testCaseResultSessionTemplate = testCaseResultSessionTemplate;
213 }
214
215 /***
216 * Gets the data row template to be used to generate the data drivable result.
217 * This is searched for in the classpath.
218 * @return the data-drivable template to be used to generate the test case docs.
219 */
220 public String getTestCaseResultDataRowTemplate() {
221 return testCaseResultDataRowTemplate;
222 }
223
224 /***
225 * Sets the data row template to be used to generate the data drivable result.
226 * This is searched for in the classpath.
227 * @param testCaseResultDataRowTemplate the data-drivable template to be used to generate the test case docs.
228 * @jameleon.attribute
229 */
230 public void setTestCaseResultDataRowTemplate(String testCaseResultDataRowTemplate) {
231 this.testCaseResultDataRowTemplate = testCaseResultDataRowTemplate;
232 }
233
234 /***
235 * Gets the template to be used to generate the function result.
236 * This is searched for in the classpath.
237 * @return the entry leaf template to be used to generate the test case docs.
238 */
239 public String getTestCaseResultFunctionTemplate() {
240 return testCaseResultFunctionTemplate;
241 }
242
243 /***
244 * Sets the function result template to be used to generate the test case result.
245 * This is searched for in the classpath.
246 * @param testCaseResultFunctionTemplate The entry leaf template to be used to generate the test case docs.
247 * @jameleon.attribute
248 */
249 public void setTestCaseResultFunctionTemplate(String testCaseResultFunctionTemplate) {
250 this.testCaseResultFunctionTemplate = testCaseResultFunctionTemplate;
251 }
252
253 /***
254 * Gets the template to be used to generate the main page of the results..
255 * This is searched for in the classpath.
256 * @return the template to be used to generate the test case main page.
257 */
258 public String getTestCaseMainPageTemplate() {
259 return testCaseMainPageTemplate;
260 }
261
262 /***
263 * Sets the template to be used to generate the main page of the results..
264 * This is searched for in the classpath.
265 * @param testCaseMainPageTemplate The template.
266 * @jameleon.attribute
267 */
268 public void setTestCaseMainPageTemplate(String testCaseMainPageTemplate) {
269 this.testCaseMainPageTemplate = testCaseMainPageTemplate;
270 }
271
272 /***
273 * Gets the template to be used to generate the test case summary page of the results..
274 * This is searched for in the classpath.
275 * @return the template to be used to generate the test case summary docs.
276 */
277 public String getTestCaseSummaryTemplate() {
278 return testCaseSummaryTemplate;
279 }
280
281 /***
282 * Sets the template to be used to generate the test case summary page of the results.
283 * This is searched for in the classpath.
284 * @param testCaseSummaryTemplate The template to be used to generate the test case summary docs.
285 * @jameleon.attribute
286 */
287 public void setTestCaseSummaryTemplate(String testCaseSummaryTemplate) {
288 this.testCaseSummaryTemplate = testCaseSummaryTemplate;
289 }
290
291 /***
292 * Gets the template to be used to generate the test case result summary page.
293 * This is searched for in the classpath.
294 * @return The template to be used to generate the test case result summary page.
295 */
296 public String getTestCaseResultSummaryTemplate() {
297 return testCaseResultSummaryTemplate;
298 }
299
300 /***
301 * Sets the template to be used to generate the test case result summary page.
302 * This is searched for in the classpath.
303 * @param testCaseResultTemplate The template to be used to generate the test case result page.
304 * @jameleon.attribute
305 */
306 public void setTestCaseResultTemplate(String testCaseResultTemplate) {
307 this.testCaseResultTemplate = testCaseResultTemplate;
308 }
309
310 /***
311 * Gets the template to be used to generate the test case result page.
312 * This is searched for in the classpath.
313 * @return The template to be used to generate the test case result page.
314 */
315 public String getTestCaseResultTemplate() {
316 return testCaseResultTemplate;
317 }
318
319 /***
320 * Sets the template to be used to generate the test case result summary page.
321 * This is searched for in the classpath.
322 * @param testCaseResultSummaryTemplate The template to be used to generate the test case result summary page.
323 * @jameleon.attribute
324 */
325 public void setTestCaseResultSummaryTemplate(String testCaseResultSummaryTemplate) {
326 this.testCaseResultSummaryTemplate = testCaseResultSummaryTemplate;
327 }
328
329 /***
330 * Sets the directory where the results will be written to.
331 * @param resultsDir - the directory where the results will be written to.
332 * @jameleon.attribute
333 */
334 public void setResultsDir(File resultsDir){
335 this.resultsDir = new File(resultsDir.getPath());
336 }
337
338 /***
339 * Sets the timestamped results directory where the test case result documentation will be stored.
340 * @param timestampedResultsDir The timestamped results directory where the test case result
341 * documentation will be stored.
342 */
343 public void setTimestampedResultsDir(File timestampedResultsDir){
344 this.timestampedResultsDir = timestampedResultsDir;
345 }
346
347 /***
348 * Gets the timestamped results directory where the test case result documentation will be stored.
349 * @return the timestamped results directory where the test case result documentation will be stored.
350 */
351 public File getTimestampedResultsDir(){
352 return timestampedResultsDir;
353 }
354
355 /***
356 * Sets the test case to record the state of the application at a defined <code>event</code>
357 * @param event - An event at which the state of the application will be stored. Valid values are:
358 * <ul>
359 * <li><code>storeStateNever</code> - never store the state of the application.</li>
360 * <li><code>storeStateOnChange</code> - store the state of the applcation on any state change.</li>
361 * <li><code>storeStateOnError</code> - store the state of the application only on an error. DEFAULT</li>
362 * </ul>
363 * If the <code>event</code> is not valid, then it is not set.
364 * @jameleon.attribute
365 */
366 public void setStoreStateEvent(String event){
367 if ("storeStateNever".equalsIgnoreCase(event) ){
368 setStoreStateNever(true);
369 }else if ("storeStateOnChange".equalsIgnoreCase(event)) {
370 setStoreStateOnChange(true);
371 }else if ("storeStateOnError".equalsIgnoreCase(event) ){
372 setStoreStateOnError(true);
373 }else{
374 log.warn(event +" not recognized as a valid option for storeStateEvent! Valid options are: storeStateOnError, storeStateOnChange, storeStateNever.");
375 }
376 }
377
378 /***
379 * Sets the test case to record the state of the application whenever the application's state changes.
380 * @param all - Set to <code>true</code> to record all responses. Defaults to <code>false</code>
381 * @jameleon.attribute
382 */
383 public void setStoreStateOnChange(boolean all){
384 if (all) {
385 stateStorer.setStorableEvent(StateStorer.ON_STATE_CHANGE_EVENT);
386 }
387 }
388
389 /***
390 * Sets the test case to record the state of the application on errors.
391 * @param onError - Set to <code>true</code> to record responses ONLY on errors. Defaults to <code>true</code>
392 * @jameleon.attribute
393 */
394 public void setStoreStateOnError(boolean onError){
395 if (onError) {
396 stateStorer.setStorableEvent(StateStorer.ON_ERROR_EVENT);
397 }
398 }
399
400 /***
401 * Sets the test case to never record the state of the application.
402 * @param none - Set to <code>true</code> to never record responses. Defaults to <code>false</code>
403 * @jameleon.attribute
404 */
405 public void setStoreStateNever(boolean none){
406 if (none) {
407 stateStorer.setStorableEvent(StateStorer.ON_NO_EVENT);
408 }
409 }
410
411 /***
412 * Gets the directory where the results will be written to.
413 * @return the directory where the results will be written to.
414 */
415 public File getResultsDir(){
416 return getResultsDir(true);
417 }
418
419 /***
420 * Gets the directory where the results will be written to.
421 * @param includeName set to true to include the test case name in the results directory.
422 * @return the directory where the results will be written to.
423 */
424 public File getResultsDir(boolean includeName){
425 File dir = getTimestampedResultsDir();
426 if (dir == null){
427 if (includeName){
428 if (baseDir.equals(resultsDir.getParentFile())){
429 dir = new File(resultsDir, getName());
430 }else{
431 dir = new File(baseDir, resultsDir.getPath() + File.separator + getName());
432 }
433 }else{
434 dir = resultsDir;
435 }
436 }
437 return dir;
438 }
439
440 /***
441 * @return the directory where the results will be stored.
442 * @param rowCount The row number the test case is on
443 */
444 protected File getResultsDir(int rowCount){
445 return new File(getResultsDir(),""+rowCount);
446 }
447
448 /***
449 * Gets the maximum execution time before the test case fails
450 * @return the maximum execution time before the test case fails
451 */
452 public long getMaxExecutionTime(){
453 return maxExecutionTime;
454 }
455
456 /***
457 * Sets the maximum execution time before the test case fails
458 * @param maxExecutionTime - the maximum execution time before the test case fails
459 * @jameleon.attribute
460 */
461 public void setMaxExecutionTime(long maxExecutionTime){
462 this.maxExecutionTime = maxExecutionTime;
463 }
464
465 /***
466 * @return the <code>StateStorer</code> instance created by this TestCaseTag.
467 */
468 public StateStorer getStateStorer(){
469 return stateStorer;
470 }
471
472 /***
473 * Used internally to mark whether the DataDrivable had a problem. This is used for the failOnCSVFileNotFound option only
474 * @param failedOnDataDriver - Set to <code>true</code> if a problem due to DataDrivable occured.
475 * @jameleon.attribute
476 */
477 public void setFailedOnDataDriver(boolean failedOnDataDriver){
478 this.failedOnDataDriver = failedOnDataDriver;
479 }
480
481 /***
482 * Sets the failOnCSVFileNotFound property
483 * @param failOnCSVFileNotFound - Set to <code>false</code> to not log a failure due to a CSV FileNotFoundException
484 * @jameleon.attribute
485 */
486 public void setFailOnCSVFileNotFound(boolean failOnCSVFileNotFound){
487 this.failOnCSVFileNotFound = failOnCSVFileNotFound;
488 }
489
490 /***
491 * @return the list of keys that have been put into the context.
492 */
493 public List getKeySet() {
494 return keysSet;
495 }
496
497 /***
498 * @return the organization or affiliate this application will be tested against.
499 * This would used for when there is one application for many different datasources
500 * like our banking applications that one day will run for multiple banks. This is also
501 * important because the URLs change between organizations as well.
502 */
503 public String getOrganization() {
504 return organization;
505 }
506
507 /***
508 * Sets the organziation or company that this test will be run against.
509 * This is used the same as the testEnvironment to find the CSV file or use values
510 * from a properties file specific to an organization.
511 * @param organization - The organziation or company that this test will be run against.
512 * @jameleon.attribute
513 */
514 public void setOrganization(String organization) {
515 this.organization = organization;
516 }
517
518 /***
519 * @return the environment the test cases will be run under
520 */
521 public String getTestEnvironment() {
522 return this.testEnvironment;
523 }
524
525 /***
526 * Sets the environment to which testing system the testcase will be run in.
527 * Information like the starting url, can be based on this. This is
528 * also used to find the CSV file if one is used for the test case.
529 * This can be set as a global variable.
530 * @param testEnvironment Some examples might include, localhost, dev, test, stage, production ...:
531 * @jameleon.attribute
532 */
533 public void setTestEnvironment(String testEnvironment) {
534 this.testEnvironment = testEnvironment;
535 }
536
537 /***
538 * @return the test cases that were run under this environment
539 */
540 public TestCaseResult getResults() {
541 return results;
542 }
543
544 public void setResults(TestCaseResult results){
545 this.results = results;
546 }
547
548 /***
549 * Sets whether a CSV file should be used for this testcase or not.
550 * @param useCSV - If set to true, then this test case will grab all of it's data from a CSV file
551 * @jameleon.attribute
552 */
553 public void setUseCSV(boolean useCSV) {
554 this.useCSV = useCSV;
555 }
556
557 /***
558 * Sets whether a std out message should be sent before and after the execution of a functional point.
559 * @param trace - If set to true, then this test case will show messages before and after execution of each functional point.
560 * @jameleon.attribute
561 */
562 public void setTrace(boolean trace) {
563 this.trace = trace;
564 }
565
566 /***
567 * Gets whether a std out message should be sent before and after the execution of a functional point.
568 * @return true, if this test case will show messages before and after execution of each functional point.
569 */
570 public boolean getTrace() {
571 return trace;
572 }
573
574 /***
575 * Sets the base directory of the project
576 * @param baseDir - The base directory of the project
577 * @jameleon.attribute
578 */
579 public void setBaseDir(File baseDir) {
580 this.baseDir = baseDir;
581 }
582
583 /***
584 * @return The base directory of the project
585 */
586 public File getBaseDir() {
587 return baseDir;
588 }
589
590 /***
591 * Gets the directory of where csv files should be read from,
592 * given the environment settings.
593 * @param calculate - set to true to calculate in testEnvironment and organization if they apply
594 * @return The csv file to run the test against
595 */
596 public File getCsvDir(boolean calculate){
597 File dir;
598 if (calculate) {
599 dir = getCsvDir();
600 }else{
601 String filename = baseDir.getPath() + File.separator + dataDir.getPath() + File.separator;
602 dir = new File(filename);
603 }
604 return dir;
605 }
606
607
608 /***
609 * @return the level of the asserts to run that are greater than this number.
610 */
611 public String getAssertGreaterThanLevel() {
612 return this.assertGreaterThanLevel;
613 }
614
615 /***
616 * @param assertGreaterThanLevel the level of the asserts to run that are greater than this number.
617 * @jameleon.attribute
618 */
619 public void setAssertGreaterThanLevel(String assertGreaterThanLevel) {
620 this.assertGreaterThanLevel = assertGreaterThanLevel;
621 }
622
623 /***
624 * @return the level of the asserts to run that are less than this number.
625 */
626 public String getAssertLessThanLevel() {
627 return this.assertLessThanLevel;
628 }
629
630 /***
631 * @param assertLessThanLevel the level of the asserts to run that are less than this number.
632 * @jameleon.attribute
633 */
634 public void setAssertLessThanLevel(String assertLessThanLevel) {
635 this.assertLessThanLevel = assertLessThanLevel;
636 }
637
638 /***
639 * @return the level of the asserts to run that are equal to this number.
640 */
641 public String getAssertLevel() {
642 return this.assertLevel;
643 }
644
645 /***
646 * @param assertLevel the level of the asserts to run that are equal to this number.
647 * @jameleon.attribute
648 */
649 public void setAssertLevel(String assertLevel) {
650 this.assertLevel = assertLevel;
651 }
652
653 /***
654 * @return the level of the asserts to run that are equal to this list of numbers.
655 */
656 public String getAssertLevels() {
657 return this.assertLevels;
658 }
659
660 /***
661 * Sets the level of the asserts to run that are equal to this list of numbers.
662 * @param assertLevels A list of assert levels to run in the this test case
663 * @jameleon.attribute
664 */
665 public void setAssertLevels(String assertLevels) {
666 this.assertLevels = assertLevels;
667 }
668
669 /***
670 * Sets the test case to generate the test case docs based on the javadocs of the functional points
671 * and the functionId's of the functional points in the test case.
672 * @param genTestCaseDocs - set to true if the test case docs are to be generated
673 * @jameleon.attribute
674 */
675 public void setGenTestCaseDocs(boolean genTestCaseDocs){
676 this.genTestCaseDocs = genTestCaseDocs;
677 }
678
679 /***
680 * @return The test case for documentation purposes.
681 */
682 public TestCase getTestCase(){
683 return testCase;
684 }
685
686 /***
687 * @return true if the test is actually supposed to be executed. The default is <code>true</code>
688 */
689 public boolean isExecuteTestCase(){
690 return executeTestCase;
691 }
692
693 /***
694 * @return true if the test case results docs are to be generated.
695 * The default is <code>true</code>
696 */
697 public boolean isGenTestCaseDocs(){
698 return genTestCaseDocs;
699 }
700
701 /***
702 * Sets the test case to be executed or not.
703 * @param executeTestCase - Set to <code>false</code> if the functionality of the test case is not to be executed.
704 * The default is <code>true</code>. This would be set to false if only the test case docs are to be generated.
705 * @jameleon.attribute
706 */
707 public void setExecuteTestCase(boolean executeTestCase){
708 this.executeTestCase = executeTestCase;
709 }
710
711 /***
712 * Sets the url of the bugtracking tool used so the bug's listed for the test case are linked.
713 * @return the bug tracker url
714 */
715 public String getBugTrackerUrl(){
716 return this.bugTrackerUrl;
717 }
718
719 /***
720 * Sets the url of the bugtracking tool used so the bug's listed for the test case are linked.
721 * @param bugTrackerUrl The url of the bugtracking tool used so the bug's listed for the test case are linked.
722 * @jameleon.attribute
723 */
724 public void setBugTrackerUrl(String bugTrackerUrl){
725 this.bugTrackerUrl = bugTrackerUrl;
726 }
727
728 /***
729 * @return the character set encoding to use for the test case in XML.
730 */
731 public String getGenTestCaseDocsEncoding(){
732 return genTestCaseDocsEncoding;
733 }
734
735 /***
736 * Sets the charset encoding.
737 * @param encoding - the character set encoding to use for the test case in XML.
738 * @jameleon.attribute
739 */
740 public void setGenTestCaseDocsEncoding(String encoding){
741 this.genTestCaseDocsEncoding = encoding;
742 }
743
744 /***
745 * Gets the name of the properties file (minus the .properties) to read in into the context
746 * @return the name of the properties file (minus the .properties) to read in into the context
747 */
748 public String getPropsName(){
749 return propsName;
750 }
751
752 /***
753 * Sets the name of the properties file (minus the .properties) to read in into the context
754 * @param propsName - name of the properties file (minus the .properties) to read in into the context
755 * @jameleon.attribute
756 */
757 public void setPropsName(String propsName){
758 this.propsName = propsName;
759 }
760
761 /***
762 * Sets the configuration file Jameleon uses to configure itself.
763 * (default is defined in {@link net.sf.jameleon.util.Configurator})
764 * Do not use. This is used for internal testing purposes only.
765 * @param configName - the configuration file Jameleon uses to configure itself.
766 * @jameleon.attribute
767 */
768 public void setJameleonConfigName(String configName) {
769 jameleonConfigName = configName;
770 Configurator.clearInstance();
771 Configurator config = Configurator.getInstance();
772 config.setConfigName(jameleonConfigName);
773 }
774
775 public String getJameleonConfigName(){
776 return jameleonConfigName;
777 }
778
779 /***
780 * Query whether SSL cert validity checking is enabled.
781 * @return - If true, an exception will be thrown when an invalid SSL cert is encountered.
782 * If false, invalid SSL certs will be accepted.
783 */
784 public boolean isEnableSslCertCheck() {
785 return enableSslCertCheck;
786 }
787 /***
788 * Enable or disable validity checking of SSL certificates.
789 * @param enable - If "true", an exception will be thrown when an invalid SSL cert is encountered.
790 * If "false", invalid SSL certs will be accepted. If not set, the default is "true".
791 * @jameleon.attribute
792 */
793 public void setEnableSslCertCheck(boolean enable) {
794 enableSslCertCheck = enable;
795 }
796
797 protected void setLocationAware(LocationAware la){
798 if (la.getLineNumber() == -1) {
799 la.setColumnNumber(getColumnNumber());
800 la.setLineNumber(getLineNumber());
801 }
802 if (la.getFileName() == null) {
803 la.setFileName(getFileName());
804 }
805 }
806
807 /***
808 * Execute the rest of the test script.
809 * @param rowNum - if a csv file is used, then this pertains to the row number
810 * the test case is being executed against. If a CSV file is not
811 * used, then 0 is sent.
812 * @param result - the results to run with.
813 */
814 public void invokeChildren(int rowNum, JameleonTestResult result){
815 try {
816 invokeBody(xmlOut);
817 } catch (JellyTagException jte){
818 Throwable err = jte;
819 LocationAware la = jte;
820 if (err.getCause() != null && err.getCause() instanceof LocationAware) {
821 err = err.getCause();
822 la = (LocationAware)err;
823 }
824 setLocationAware(la);
825 JameleonScriptException jse = new JameleonScriptException(err.getMessage(), la);
826 logError(err);
827 result.setError(jse);
828 } catch (ThreadDeath td){
829 throw td;
830 } catch (Throwable t) {
831 if (trace){
832 log.info(JameleonUtility.getStack(t));
833 }
834 LocationAware la = null;
835 Throwable err = t;
836 if (t.getCause() != null && t.getCause() instanceof LocationAware) {
837 err = t.getCause();
838 la = (LocationAware)err;
839 }else if (t instanceof LocationAware) {
840 la = (LocationAware)t;
841 }
842
843 JameleonScriptException jse = null;
844 if (la != null) {
845 setLocationAware(la);
846 jse = new JameleonScriptException(err.getMessage(), la);
847 }
848 if (jse != null) {
849 err = jse;
850 }
851 logError(err);
852 result.setError(t);
853 } finally{
854 result.setExecutionTime(System.currentTimeMillis() - startTime);
855 }
856 failedOnCurrentRow = false;
857 }
858
859 /***
860 * A CsvExecutable implementation method that gets called once for every row in the csv file.
861 * This does not include the top row which should only define the variable names
862 */
863 public void executeDrivableRow(int rowNum){
864 traceMsg("BEGIN executing row number "+rowNum);
865 TestResultWithChildren res = results;
866 if (rowResultContainer != null) {
867 res = rowResultContainer;
868 }
869 ddRowResult = new CountableDataDrivableRowResult(fp, res);
870 ddRowResult.copyLocationAwareProperties(this);
871 ddRowResult.setRowData(new HashMap(rowData));
872 ddRowResult.setRowNum(rowNum);
873 stateStorer.setStoreDir(getResultsDir(rowNum));
874 invokeChildren(rowNum, ddRowResult);
875 traceMsg("END executing row number "+rowNum);
876 }
877
878 protected void initResults(){
879 results = new TestCaseResult(getFunctionalPoint());
880 results.copyLocationAwareProperties(this);
881 if (useCSV) {
882 rowResultContainer = new CountableDataDrivableResultContainer(getFunctionalPoint(), results);
883 rowResultContainer.copyLocationAwareProperties(this);
884 }
885 }
886
887 /***
888 * Set up the test environment.
889 *
890 * @throws JellyTagException if the tag is not in the correct state.
891 */
892 public void setUp() throws JellyTagException{
893 initResults();
894 String script = getFileName();
895 if (script != null) {
896 testCase.readFromScript(script);
897 }
898
899
900 if (testCase.getName() != null) {
901
902 name = testCase.getName();
903 }
904 results.setTestName(name);
905 loadJameleonConfig();
906
907
908 testCase.setOrganization(organization);
909 testCase.setTestEnvironment(testEnvironment);
910
911 if (propsName != null) {
912 Properties p = new Properties();
913 getPropertiesForName(propsName, p);
914 inilializeProps(p);
915 }
916 validateAttributes();
917 setAssertLevels();
918 if (organization != null && organization.trim().length() > 0) {
919 context.setVariable("organization", organization);
920 }
921 setUpDataDrivable();
922 }
923 /***
924 * Records a child result. Used as a helper method for the ResultRecordable
925 * implementation methods. This method overrides the default behavior of
926 * AbstractDataDrivableTag.
927 * @param result - the result to record
928 */
929 protected void recordResult(JameleonTestResult result){
930 if (ddRowResult != null) {
931 ddRowResult.addChildResult(result);
932 result.setParentResults(ddRowResult);
933 }else if (results != null) {
934 results.addChildResult(result);
935 result.setParentResults(results);
936 }
937 }
938
939 /***
940 * This method executes the tags inside the test-case tag. If <code>useCSV=true</code>, then the data for
941 * the functional points will be grabbed from a CSV file. Otherwise the attributes are all expected to be set
942 * the contained functional points. If the functional points' attributes are set and useCSV is set to true,
943 * then the data in the CSV file will override the data set in the function points' attributes.
944 */
945 public void doTag(XMLOutput out) throws MissingAttributeException, JellyTagException{
946 xmlOut = out;
947 init();
948 setUp();
949 eventHandler.beginTestCase(this);
950
951 setStateStoreOptions();
952 try{
953 testForUnsupportedAttributesCaught();
954 broker.transferAttributes(context);
955 broker.validate(context);
956 }catch(JameleonScriptException jse){
957 results.setError(jse);
958 log.info(results);
959 eventHandler.endTestCase(this);
960 tearDown();
961 throw jse;
962 }
963 try{
964 if (executeTestCase) {
965 if (useCSV) {
966 spanCSV();
967 }else{
968 executeNoCSV();
969 }
970 if (maxExecutionTime > 0 && maxExecutionTime < results.getExecutionTime()) {
971 String msg = "The maximum execution time <"+maxExecutionTime+"> was exceeded <"+results.getExecutionTime()+">!";
972 results.setError(new JameleonScriptException(msg, this));
973 }
974 if ( failOnCSVFileNotFound || (!failOnCSVFileNotFound && !failedOnDataDriver) ) {
975 if (results.failed()) {
976 JellyTagException jte = null;
977 if (results.getError() != null) {
978 jte = createExceptionFromResult(results);
979 }else{
980 StringBuffer sb = new StringBuffer();
981 Iterator it = results.getFailedResults().iterator();
982 JameleonTestResult result;
983 while (it.hasNext()) {
984 result = (JameleonTestResult)it.next();
985 sb.append(result.getTag().getDefaultTagName()).append(":");
986 sb.append(result.getErrorMsg());
987 if (jte == null) {
988 jte = createExceptionFromResult(result);
989 }
990 }
991 }
992 if (jte != null) {
993 if (results.getError() != null) {
994 results.setError(jte);
995 }
996 throw jte;
997 }
998 }
999 }else{
1000 setGenTestCaseDocs(false);
1001 }
1002 }
1003 }finally{
1004 eventHandler.endTestCase(this);
1005 if ( failOnCSVFileNotFound || (!failOnCSVFileNotFound && !failedOnDataDriver) ) {
1006 log.info(results);
1007 }
1008 tearDown();
1009 }
1010 }
1011
1012 protected JellyTagException createExceptionFromResult(JameleonTestResult jtr){
1013 return createExceptionFromResult(jtr.getErrorMsg(), jtr);
1014 }
1015
1016 protected JellyTagException createExceptionFromResult(String message, JameleonTestResult jtr){
1017 return new JellyTagException(message, jtr.getError(), jtr.getFileName(), jtr.getElementName(), jtr.getColumnNumber(), jtr.getLineNumber());
1018 }
1019
1020 protected void executeNoCSV(){
1021 executeBody(new Runnable(){
1022 public void run() {
1023 invokeChildren(1, results);
1024 }});
1025 }
1026
1027 /***
1028 * useCSV is set to true, data-drive this CSV file.
1029 */
1030 protected void spanCSV(){
1031 final DataDrivable dd = this;
1032 csv.setFile(getCsvFile());
1033 executeBody(new Runnable(){
1034 public void run() {
1035 try{
1036 traceMsg("Begin parsing: \""+getCsvFile()+"\"");
1037 executer.executeData(dd, false);
1038 traceMsg("End parsing \""+getCsvFile()+"\"");
1039 }catch(FileNotFoundException fnfe){
1040 failedOnDataDriver = true;
1041 results.setError(fnfe);
1042 }catch(IOException ioe){
1043 failedOnDataDriver = true;
1044 results.setError(new JameleonScriptException(" Trouble parsing "+getCsvFile()));
1045 }catch(IllegalStateException ise){
1046 failedOnDataDriver = true;
1047 results.setError(ise);
1048 }catch(RuntimeException re){
1049 if (re.getCause() != null) {
1050 results.setError(re.getCause());
1051 }
1052 }
1053 }});
1054 }
1055
1056 /***
1057 * Clean things up after the test case has been executed.
1058 */
1059 public void tearDown(){
1060 Configurator.clearInstance();
1061 Iterator it = keysSet.iterator();
1062 while (it.hasNext()) {
1063 context.removeVariable((String)it.next());
1064 }
1065 results.setTag(fp.cloneFP());
1066 resetFunctionalPoint();
1067 }
1068
1069 protected void setStateStoreOptions(){
1070 stateStorer.setStoreDir(getTimestampedResultsDir());
1071 }
1072
1073 protected void executeBody(Runnable r){
1074 startTime = System.currentTimeMillis();
1075 try{
1076 r.run();
1077 }finally{
1078 results.setExecutionTime(System.currentTimeMillis() - startTime);
1079 }
1080 }
1081
1082 /***
1083 * Gets the results file that represents the test case execution.
1084 * @return the results file that represents the test case execution.
1085 */
1086 public File getResultsFile(){
1087 if (genTestCaseDocs && resultsFile == null) {
1088 resultsFile = new File(getResultsDir(), File.separator+"index.html");
1089 }
1090 return resultsFile;
1091 }
1092
1093 private InputStream getInputStream(String fileName){
1094 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
1095 InputStream input = null;
1096 if (classLoader == null) {
1097 classLoader = this.getClass().getClassLoader();
1098 } else {
1099 if (classLoader.getResourceAsStream( fileName ) == null) {
1100 classLoader = this.getClass().getClassLoader();
1101 }
1102 }
1103 if (classLoader != null) {
1104 input = classLoader.getResourceAsStream(fileName);
1105 }
1106 return input;
1107 }
1108
1109
1110 /***
1111 * This method creates a new Properties Object populated only with the
1112 * properties for application desired. The keys for these properties are
1113 * no longer start with the application name. For example, if a property
1114 * named "personalBanking.host" exists in the original Properties Object
1115 * and this method is called with "personalBanking" as the <code>applicationName</code>
1116 * then the new property name will be "host".
1117 * @param applicationName The name of the application that you want properties for.
1118 * @return a Properties Object populated only with the
1119 * properties for application desired
1120 */
1121 public Properties getPropertiesForApplication(String applicationName) {
1122 ResourceBundle props = null;
1123 try {
1124 props = loadApplicationProperties();
1125 } catch (IOException mre) {
1126 if (testEnvironment != null && testEnvironment.length() > 1) {
1127 traceMsg("not reading in "+testEnvironment+"-Applications.properties");
1128 }
1129 }
1130 Properties p = new Properties();
1131 String keyWithOrg = null;
1132 if (organization != null && !organization.trim().equals("")) {
1133 keyWithOrg = organization+"."+applicationName;
1134 }
1135 if (props != null) {
1136 addValuesFromResourceBundle(props, p, keyWithOrg, true);
1137 addValuesFromResourceBundle(props, p, applicationName, false);
1138 }
1139 getPropertiesForName(applicationName, p);
1140
1141 try{
1142 ResourceBundle appProps = getResourceBundle(applicationName);
1143 if (organization != null && organization.trim().length() > 0) {
1144 addValuesFromResourceBundle(appProps, p, organization, false);
1145 }
1146 addValuesFromResourceBundle(appProps, p, "", false);
1147 }catch(IOException mre){
1148
1149 }
1150 try{
1151 ResourceBundle baseProps = getResourceBundle("Applications");
1152 addValuesFromResourceBundle(baseProps, p, keyWithOrg, false);
1153 addValuesFromResourceBundle(baseProps, p, applicationName, false);
1154 }catch(IOException mre){
1155
1156 }
1157 inilializeProps(p);
1158 substituteValues(p);
1159 return p;
1160 }
1161
1162 private void inilializeProps(Properties p){
1163 Iterator it = rowData.keySet().iterator();
1164 String key, value;
1165 while (it.hasNext()) {
1166 key = (String)it.next();
1167 value = (String)rowData.get(key);
1168 if (value != null) {
1169 p.setProperty(key, value);
1170 }
1171 }
1172 }
1173
1174 private void getPropertiesForName(String propsName, Properties p){
1175 try{
1176 ResourceBundle appProps = getResourceBundle(propsName);
1177 if (organization != null && organization.trim().length() > 0) {
1178 addValuesFromResourceBundle(appProps, p, organization, false);
1179 }
1180 addValuesFromResourceBundle(appProps, p, "", false);
1181 }catch(IOException mre){
1182
1183 }
1184 }
1185
1186 /***
1187 * Do variable substition based on keys in the iterator.
1188 * @param p - the properties to be substituted.
1189 */
1190 protected void substituteValues(Properties p){
1191 JexlExpressionFactory exfact = new JexlExpressionFactory();
1192 String key, value;
1193 Iterator it = p.keySet().iterator();
1194 while (it.hasNext()) {
1195 key = (String) it.next();
1196 value = p.getProperty(key);
1197 if (value != null) {
1198 try{
1199 Expression ex = CompositeExpression.parse(value, exfact);
1200 context.setVariable(key, ex.evaluateAsString(context));
1201 }catch(JellyException je){
1202 je.printStackTrace();
1203 }
1204 }
1205 }
1206 }
1207
1208 /***
1209 * Do variable subsitution on values stored from data file.
1210 */
1211 public void substituteKeyValues(){
1212 JexlExpressionFactory exfact = new JexlExpressionFactory();
1213 String key, value;
1214 Object obj;
1215 Set keys = rowData.keySet();
1216 Iterator it = keys.iterator();
1217 while (it.hasNext()) {
1218 key = (String) it.next();
1219 obj = rowData.get(key);
1220
1221 if (obj instanceof String) {
1222 value = (String)rowData.get(key);
1223 if (value != null) {
1224 try{
1225 Expression ex = CompositeExpression.parse(value, exfact);
1226 context.setVariable(key, ex.evaluateAsString(context));
1227 }catch(JellyException je){
1228 je.printStackTrace();
1229 }
1230 }
1231 }
1232 }
1233 }
1234
1235
1236 protected void addValuesFromResourceBundle(ResourceBundle props, Properties p, String startOfKey, boolean overrideValue){
1237 Enumeration e = props.getKeys();
1238 String key, newKey;
1239 String keyToFind = (startOfKey != null && startOfKey.length() > 0 ) ? startOfKey+"." : "";
1240 while (e.hasMoreElements()) {
1241 key = (String)e.nextElement();
1242 if (key != null && key.startsWith(keyToFind)) {
1243 newKey = key.substring(keyToFind.length());
1244 if (p.getProperty(newKey) == null || overrideValue) {
1245 p.setProperty(newKey, props.getString(key));
1246 }
1247
1248 if ( context != null && (overrideValue || context.getVariable(newKey) == null) ) {
1249 keysSet.add(newKey);
1250 context.setVariable(newKey, props.getString(key));
1251 }
1252 }
1253 }
1254 }
1255
1256 /***
1257 * Validate the parameters
1258 * @throws MissingAttributeException If a parameter is invalid.
1259 */
1260 protected void validateAttributes() throws MissingAttributeException{
1261 if (name == null) {
1262 throw new MissingAttributeException("name");
1263 }
1264 if (!isValidateAssertLevel(assertGreaterThanLevel)) {
1265 throw new MissingAttributeException("assertGreaterThanLevel must be between SMOKE and REGRESSION");
1266 }
1267 if (!isValidateAssertLevel(assertLessThanLevel)) {
1268 throw new MissingAttributeException("assertLessThanLevel must be between SMOKE and REGRESSION");
1269 }
1270 if (!isValidateAssertLevel(assertLevel)) {
1271 throw new MissingAttributeException("assertLevel must be between SMOKE and REGRESSION");
1272 }
1273 if (assertLevels != null) {
1274 String[] levels = parseAssertLevels();
1275 for (int i = 0; i < levels.length; i++) {
1276 if (!isValidateAssertLevel(levels[i])) {
1277 throw new MissingAttributeException("assertLevels must be between SMOKE and REGRESSION");
1278 }
1279 }
1280 }
1281 }
1282
1283 protected String[] parseAssertLevels() {
1284 return assertLevels.split(", ?");
1285 }
1286
1287 /***
1288 * @param level - the assert level to check
1289 * @return true if the assertLevel is a valid assert level. Must begin with "LEVEL" followed by a digit 1-9.
1290 */
1291 protected boolean isValidateAssertLevel(String level) {
1292 return(getAssertLevelFromString(level) != AssertLevel.INVALID_LEVEL);
1293 }
1294
1295 protected void setAssertLevels() {
1296 AssertLevel al = AssertLevel.getInstance();
1297 if (assertGreaterThanLevel != null) {
1298 al.setGreaterThanLevel(getAssertLevelFromString(assertGreaterThanLevel));
1299 }
1300 if (assertLessThanLevel != null) {
1301 al.setLessThanLevel(getAssertLevelFromString(assertLessThanLevel));
1302 }
1303 if (assertLevel != null) {
1304 al.setLevel(getAssertLevelFromString(assertLevel));
1305 }
1306 if (assertLevels != null && assertLevels.length() > 0) {
1307 String[] levels = parseAssertLevels();
1308 for (int i = 0; i < levels.length; i++) {
1309 al.addLevel(getAssertLevelFromString(levels[i]));
1310 }
1311 }
1312 }
1313
1314 protected int getAssertLevelFromString(String assertLevel) {
1315 int lvl = -1;
1316
1317 if (assertLevel == null || "".equals(assertLevel)) {
1318 lvl = AssertLevel.NO_LEVEL;
1319 } else if ("SMOKE".equalsIgnoreCase(assertLevel)) {
1320 lvl = AssertLevel.SMOKE;
1321 } else if ("LEVEL1".equalsIgnoreCase(assertLevel)) {
1322 lvl = AssertLevel.LEVEL1;
1323 } else if ("LEVEL2".equalsIgnoreCase(assertLevel)) {
1324 lvl = AssertLevel.LEVEL2;
1325 } else if ("FUNCTION".equalsIgnoreCase(assertLevel)) {
1326 lvl = AssertLevel.FUNCTION;
1327 } else if ("LEVEL4".equalsIgnoreCase(assertLevel)) {
1328 lvl = AssertLevel.LEVEL4;
1329 } else if ("LEVEL5".equalsIgnoreCase(assertLevel)) {
1330 lvl = AssertLevel.LEVEL5;
1331 } else if ("REGRESSION".equalsIgnoreCase(assertLevel)) {
1332 lvl = AssertLevel.REGRESSION;
1333 } else if ("LEVEL7".equalsIgnoreCase(assertLevel)) {
1334 lvl = AssertLevel.LEVEL7;
1335 } else if ("LEVEL8".equalsIgnoreCase(assertLevel)) {
1336 lvl = AssertLevel.LEVEL8;
1337 } else if ("ACCEPTANCE".equalsIgnoreCase(assertLevel)) {
1338 lvl = AssertLevel.ACCEPTANCE;
1339 } else {
1340 lvl = AssertLevel.INVALID_LEVEL;
1341 }
1342 return lvl;
1343 }
1344
1345 /***
1346 * Loads the properties for all applications.
1347 * @return The ResourceBundle representing the given application.
1348 * @throws IOException When the given file can not be found in the classpath
1349 */
1350 protected ResourceBundle loadApplicationProperties() throws IOException{
1351 String resName = testEnvironment+"-"+APPLICATIONS_PROPERTIES;
1352 return getResourceBundle(resName);
1353 }
1354
1355 /***
1356 * Loads the properties for all applications.
1357 * @param filename - the name of the file
1358 * @return a ResourceBundle loaded from the class path.
1359 * @throws IOException when the file can not be found or loaded
1360 */
1361 private ResourceBundle getResourceBundle(String filename) throws IOException{
1362 String resName = filename+".properties";
1363 InputStream in = getInputStream(resName);
1364 if (in != null) {
1365 return new PropertyResourceBundle (getInputStream(resName));
1366 }else{
1367 throw new IOException("Couldn't find "+filename);
1368 }
1369 }
1370
1371 protected void setValueFromEnvironment(Configurator config, String key){
1372 String value = config.getValue(key);
1373 if (value != null){
1374 try{
1375 BeanUtils.copyProperty(this, key, value);
1376 }catch (Exception e){
1377
1378 }
1379 }
1380 }
1381
1382 protected void setFileFromEnvironment(Configurator config, String key){
1383 String value = config.getValue(key);
1384 if (value != null && key != null){
1385 try{
1386 File file = new File(value);
1387 File tmpFile;
1388 if (key.equals("baseDir")) {
1389 tmpFile = file;
1390 }else{
1391 tmpFile = new File(baseDir, value);
1392 }
1393 if ( tmpFile.exists() ) {
1394 BeanUtils.copyProperty(this, key, file);
1395 }
1396 if (!tmpFile.exists()) {
1397 log.warn("The " + value + " directory does not exist for "+key);
1398 log.warn("Leaving "+key+" to it's default value!");
1399 }
1400 }catch(Exception iae){
1401
1402 }
1403 }
1404 }
1405
1406 /***
1407 * Loads the properties for all applications.
1408 */
1409 protected void loadJameleonConfig() {
1410 String[] vars = {"testEnvironment","organization","assertGreaterThanLevel",
1411 "assertLessThanLevel","assertLevels","assertLevel","bugTrackerUrl",
1412 "genTestCaseDocsEncoding", "csvCharset", "trace","genTestCaseDocs",
1413 "failOnCSVFileNotFound","executeTestCase", "enableSslCertCheck",
1414 "storeStateEvent","genTestCaseDocsTemplate", "includeTagDetailsInResults"};
1415 String[] fileVars = {"baseDir","resultsDir","csvDir"};
1416 try {
1417 Configurator config = Configurator.getInstance();
1418 config.setConfigName(getJameleonConfigName());
1419 for (int i = 0; i < vars.length; i++) {
1420 setValueFromEnvironment(config, vars[i]);
1421 }
1422 for (int i = 0; i < fileVars.length; i++) {
1423 setFileFromEnvironment(config, fileVars[i]);
1424 }
1425 } catch (ThreadDeath td){
1426 throw td;
1427 } catch (Throwable t) {
1428
1429 }
1430 }
1431
1432 /***
1433 * Logs an error in XML format for easy interpreting later. The error message prints out all known
1434 * environment variables and information.
1435 * @param t The Error to log and set in the result
1436 */
1437 protected void logError(Throwable t) {
1438 results.setError(t);
1439 log.debug(JameleonUtility.getStack(t));
1440 }
1441
1442 }