• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id$
20  */
21 
22 /*
23  *
24  * Reporter.java
25  *
26  */
27 package org.apache.qetest;
28 
29 import org.apache.test.android.TestLogger;
30 
31 import java.io.File;
32 import java.io.FileWriter;
33 import java.io.IOException;
34 import java.io.PrintWriter;
35 import java.lang.reflect.Constructor;
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Method;
38 import java.util.Enumeration;
39 import java.util.Hashtable;
40 import java.util.Properties;
41 import java.util.StringTokenizer;
42 
43 /**
44  * Class defining how a test can report results including convenience methods.
45  * <p>Tests generally interact with a Reporter, which turns around to call
46  * a Logger to actually store the results.  The Reporter serves as a
47  * single funnel for all results, hiding both the details and number of
48  * actual loggers that might currently be turned on (file, screen, network,
49  * etc.) from the test that created us.</p>
50  * <p>Note that Reporter adds numerous convenience methods that, while they
51  * are not strictly necessary to express a test's results, make coding
52  * tests much easier.  Reporter is designed to be subclassed for your
53  * particular application; in general you only need to provide setup mechanisims
54  * specific to your testing/product environment.</p>
55  * @todo all methods should check that available loggers are OK
56  * @todo explain better how results are rolled up and calculated
57  * @author Shane_Curcuru@lotus.com
58  * @author Jo_Grant@lotus.com
59  * @version $Id$
60  */
61 public class Reporter implements Logger
62 {
63 
64     /**
65      * Parameter: (optional) Name of results summary file.
66      * <p>This is a custom parameter optionally used in writeResultsStatus.</p>
67      */
68     public static final String OPT_SUMMARYFILE = "summaryFile";
69 
70 
71     /**
72      * Constructor calls initialize(p).
73      * @param p Properties block to initialize us with.
74      */
Reporter(Properties p)75     public Reporter(Properties p)
76     {
77         ready = initialize(p);
78 
79         // Android-changed: Hard-coded AndroidLogger here. Otherwise, all test failures are silent.
80         addLogger(TestLogger.class.getName(), null);
81     }
82 
83     /** If we're ready to start outputting yet. */
84     protected boolean ready = false;
85 
86     //-----------------------------------------------------
87     //-------- Implement Logger Control and utility routines --------
88     //-----------------------------------------------------
89 
90     /**
91      * Return a description of what this Logger/Reporter does.
92      * @author Shane_Curcuru@lotus.com
93      * @return description of how this Logger outputs results, OR
94      * how this Reporter uses Loggers, etc..
95      */
getDescription()96     public String getDescription()
97     {
98         return "Reporter: default reporter implementation";
99     }
100 
101     /**
102      * Returns information about the Property name=value pairs that
103      * are understood by this Logger/Reporter.
104      * @author Shane_Curcuru@lotus.com
105      * @return same as {@link java.applet.Applet.getParameterInfo}.
106      */
getParameterInfo()107     public String[][] getParameterInfo()
108     {
109 
110         String pinfo[][] =
111         {
112             { OPT_LOGGERS, "String", "FQCN of Loggers to add" },
113             { OPT_LOGFILE, "String",
114               "Name of file to use for file-based Logger output" },
115             { OPT_LOGGINGLEVEL, "int",
116               "to setLoggingLevel() to control amount of output" },
117             { OPT_PERFLOGGING, "boolean",
118               "if we should log performance data as well" },
119             { OPT_INDENT, "int",
120               "number of spaces to indent for supporting Loggers" },
121             { OPT_DEBUG, "boolean", "generic debugging flag" }
122         };
123 
124         return pinfo;
125     }
126 
127     /**
128      * Accessor methods for a properties block.
129      * @return our Properties block.
130      * @todo should this clone first?
131      */
getProperties()132     public Properties getProperties()
133     {
134         return reporterProps;
135     }
136 
137     /**
138      * Accessor methods for a properties block.
139      * Always having a Properties block allows users to pass common
140      * options to a Logger/Reporter without having to know the specific
141      * 'properties' on the object.
142      * <p>Much like in Applets, users can call getParameterInfo() to
143      * find out what kind of properties are available.  Callers more
144      * commonly simply call initialize(p) instead of setProperties(p)</p>
145      * @author Shane_Curcuru@lotus.com
146      * @param p Properties to set (should be cloned).
147      */
setProperties(Properties p)148     public void setProperties(Properties p)
149     {
150         if (p != null)
151             reporterProps = (Properties) p.clone();
152     }
153 
154     /**
155      * Call once to initialize this Logger/Reporter from Properties.
156      * <p>Simple hook to allow Logger/Reporters with special output
157      * items to initialize themselves.</p>
158      *
159      * @author Shane_Curcuru@lotus.com
160      * @param p Properties block to initialize from.
161      * @param status, true if OK, false if an error occoured.
162      */
initialize(Properties p)163     public boolean initialize(Properties p)
164     {
165 
166         setProperties(p);
167 
168         String dbg = reporterProps.getProperty(OPT_DEBUG);
169 
170         if ((dbg != null) && dbg.equalsIgnoreCase("true"))
171         {
172             setDebug(true);
173         }
174 
175         String perf = reporterProps.getProperty(OPT_PERFLOGGING);
176 
177         if ((perf != null) && perf.equalsIgnoreCase("true"))
178         {
179             setPerfLogging(true);
180         }
181 
182         // int values need to be parsed
183         String logLvl = reporterProps.getProperty(OPT_LOGGINGLEVEL);
184 
185         if (logLvl != null)
186         {
187             try
188             {
189                 setLoggingLevel(Integer.parseInt(logLvl));
190             }
191             catch (NumberFormatException numEx)
192             { /* no-op */
193             }
194         }
195 
196         // Add however many loggers are askedfor
197         boolean b = true;
198         StringTokenizer st =
199             new StringTokenizer(reporterProps.getProperty(OPT_LOGGERS),
200                                 LOGGER_SEPARATOR);
201         int i;
202 
203         for (i = 0; st.hasMoreTokens(); i++)
204         {
205             String temp = st.nextToken();
206 
207             if ((temp != null) && (temp.length() > 1))
208             {
209                 b &= addLogger(temp, reporterProps);
210             }
211         }
212 
213         return true;
214     }
215 
216     /**
217      * Is this Logger/Reporter ready to log results?
218      * @author Shane_Curcuru@lotus.com
219      * @return status - true if it's ready to report, false otherwise
220      * @todo should we check our contained Loggers for their status?
221      */
isReady()222     public boolean isReady()
223     {
224         return ready;
225     }
226 
227     /**
228      * Flush this Logger/Reporter - should ensure all output is flushed.
229      * Note that the flush operation is not necessarily pertinent to
230      * all types of Logger/Reporter - console-type Loggers no-op this.
231      * @author Shane_Curcuru@lotus.com
232      */
flush()233     public void flush()
234     {
235 
236         for (int i = 0; i < numLoggers; i++)
237         {
238             loggers[i].flush();
239         }
240     }
241 
242     /**
243      * Close this Logger/Reporter - should include closing any OutputStreams, etc.
244      * Logger/Reporters should return isReady() = false after closing.
245      * @author Shane_Curcuru@lotus.com
246      */
close()247     public void close()
248     {
249 
250         for (int i = 0; i < numLoggers; i++)
251         {
252             loggers[i].close();
253         }
254     }
255 
256     /**
257      * Generic properties for this Reporter.
258      * <p>Use a Properties block to make it easier to add new features
259      * and to be able to pass data to our loggers.  Any properties that
260      * we recognize will be set here, and the entire block will be passed
261      * to any loggers that we control.</p>
262      */
263     protected Properties reporterProps = new Properties();
264 
265     /**
266      * This determines the amount of data actually logged out to results.
267      * <p>Setting this higher will result in more data being logged out.
268      * Values range from Reporter.CRITICALMSG (0) to TRACEMSG (60).
269      * For non-performance-critical testing, you may wish to set this high,
270      * so all data gets logged, and then use reporting tools on the test output
271      * to filter for human use (since the appropriate level is stored with
272      * every logMsg() call)</p>
273      * @see #logMsg(int, java.lang.String)
274      */
275     protected int loggingLevel = DEFAULT_LOGGINGLEVEL;
276 
277     /**
278      * Marker that a testcase is currently running.
279      * <p>NEEDSWORK: should do a better job of reporting results in cases
280      * where users might not call testCaseInit/testCaseClose in non-nested pairs.</p>
281      */
282     protected boolean duringTestCase = false;
283 
284     /**
285      * Flag if we should force loggers closed upon testFileClose.
286      * <p>Default: true.  Standalone tests can leave this alone.
287      * Test Harnesses may want to reset this so they can have multiple
288      * file results in one actual output 'file' for file-based loggers.</p>
289      */
290     protected boolean closeOnFileClose = true;
291 
292     /**
293      * Accessor method for closeOnFileClose.
294      *
295      * @return our value for closeOnFileClose
296      */
getCloseOnFileClose()297     public boolean getCloseOnFileClose()
298     {
299         return closeOnFileClose;
300     }
301 
302     /**
303      * Accessor method for closeOnFileClose.
304      *
305      * @param b value to set for closeOnFileClose
306      */
setCloseOnFileClose(boolean b)307     public void setCloseOnFileClose(boolean b)
308     {
309         closeOnFileClose = b;
310     }
311 
312     //-----------------------------------------------------
313     //-------- Test results computation members and methods --------
314     //-----------------------------------------------------
315 
316     /** Name of the current test. */
317     protected String testName;
318 
319     /** Description of the current test. */
320     protected String testComment;
321 
322     /** Number of current case within a test, usually automatically calculated. */
323     protected int caseNum;
324 
325     /** Description of current case within a test. */
326     protected String caseComment;
327 
328     /** Overall test result of current test, automatically calculated. */
329     protected int testResult;
330 
331     /** Overall test result of current testcase, automatically calculated. */
332     protected int caseResult;
333 
334     /**
335      * Counters for overall number of results - passes, fails, etc.
336      * @todo update this if we use TestResult objects
337      */
338     protected static final int FILES = 0;
339 
340     /** NEEDSDOC Field CASES          */
341     protected static final int CASES = 1;
342 
343     /** NEEDSDOC Field CHECKS          */
344     protected static final int CHECKS = 2;
345 
346     /** NEEDSDOC Field MAX_COUNTERS          */
347     protected static final int MAX_COUNTERS = CHECKS + 1;
348 
349     /**
350      * Counters for overall number of results - passes, fails, etc.
351      * @todo update this if we use TestResult objects
352      */
353     protected int[] incpCount = new int[MAX_COUNTERS];
354 
355     /** NEEDSDOC Field passCount          */
356     protected int[] passCount = new int[MAX_COUNTERS];
357 
358     /** NEEDSDOC Field ambgCount          */
359     protected int[] ambgCount = new int[MAX_COUNTERS];
360 
361     /** NEEDSDOC Field failCount          */
362     protected int[] failCount = new int[MAX_COUNTERS];
363 
364     /** NEEDSDOC Field errrCount          */
365     protected int[] errrCount = new int[MAX_COUNTERS];
366 
367 
368     //-----------------------------------------------------
369     //-------- Composite Pattern Variables And Methods --------
370     //-----------------------------------------------------
371 
372     /**
373      * Optimization: max number of loggers, stored in an array.
374      * <p>This is a design decision: normally, you might use a ConsoleReporter,
375      * some sort of file-based one, and maybe a network-based one.</p>
376      */
377     protected int MAX_LOGGERS = 3;
378 
379     /**
380      * Array of loggers to whom we pass results.
381      * <p>Store our loggers in an array for optimization, since we want
382      * logging calls to take as little time as possible.</p>
383      */
384     protected Logger[] loggers = new Logger[MAX_LOGGERS];
385 
386     /** NEEDSDOC Field numLoggers          */
387     protected int numLoggers = 0;
388 
389     /**
390      * Add a new Logger to our array, optionally initializing it with Properties.
391      * <p>Store our Loggers in an array for optimization, since we want
392      * logging calls to take as little time as possible.</p>
393      * @todo enable users to add more than MAX_LOGGERS
394      * @author Gang Of Four
395      * @param rName fully qualified class name of Logger to add.
396      * @param p (optional) Properties block to initialize the Logger with.
397      * @return status - true if successful, false otherwise.
398      */
addLogger(String rName, Properties p)399     public boolean addLogger(String rName, Properties p)
400     {
401 
402         if ((rName == null) || (rName.length() < 1))
403             return false;
404 
405         debugPrintln("addLogger(" + numLoggers + ", " + rName + " ...)");
406 
407         if ((numLoggers + 1) > loggers.length)
408         {
409 
410             // @todo enable users to add more than MAX_LOGGERS
411             return false;
412         }
413 
414         // Attempt to add Logger to our list
415         Class rClass;
416         Constructor rCtor;
417 
418         try
419         {
420             rClass = Class.forName(rName);
421 
422             debugPrintln("rClass is " + rClass.toString());
423 
424             if (p == null)
425 
426             // @todo should somehow pass along our own props as well
427             // Need to ensure Reporter and callers of this method always
428             //  coordinate the initialization of the Loggers we hold
429             {
430                 loggers[numLoggers] = (Logger) rClass.newInstance();
431             }
432             else
433             {
434                 Class[] parameterTypes = new Class[1];
435 
436                 parameterTypes[0] = java.util.Properties.class;
437                 rCtor = rClass.getConstructor(parameterTypes);
438 
439                 Object[] initArgs = new Object[1];
440 
441                 initArgs[0] = (Object) p;
442                 loggers[numLoggers] = (Logger) rCtor.newInstance(initArgs);
443             }
444         }
445         catch (Exception e)
446         {
447 
448             // @todo should we inform user why it failed?
449             // Note: the logMsg may fail since we might not have any reporters at this point!
450             debugPrintln("addLogger exception: " + e.toString());
451             logCriticalMsg("addLogger exception: " + e.toString());
452             logThrowable(CRITICALMSG, e, "addLogger exception:");
453 
454             return false;
455         }
456 
457         // Increment counter for later use
458         numLoggers++;
459 
460         return true;
461     }
462 
463     /**
464      * Return an Hashtable of all active Loggers.
465      * @todo revisit; perhaps use a Vector
466      * @reurns Hash of all active Loggers; null if none
467      *
468      * NEEDSDOC ($objectName$) @return
469      */
getLoggers()470     public Hashtable getLoggers()
471     {
472 
473         // Optimization
474         if (numLoggers == 0)
475             return (null);
476 
477         Hashtable temp = new Hashtable();
478 
479         for (int i = 0; i < numLoggers; i++)
480         {
481             temp.put(loggers[i].getClass().getName(), loggers[i]);
482         }
483 
484         return temp;
485     }
486 
487     /**
488      * Add the default Logger to this Reporter, whatever it is.
489      * <p>Only adds the Logger if numLoggers <= 0; if the user has already
490      * setup another Logger, this is a no-op (for the testwriter who doesn't
491      * want the performance hit or annoyance of having Console output)</p>
492      * @author Gang Of Four
493      * @return status - true if successful, false otherwise.
494      */
addDefaultLogger()495     public boolean addDefaultLogger()
496     {
497 
498         // Optimization - return true, since they already have a logger
499         if (numLoggers > 0)
500             return true;
501 
502         return addLogger(DEFAULT_LOGGER, reporterProps);
503     }
504 
505     //-----------------------------------------------------
506     //-------- Testfile / Testcase start and stop routines --------
507     //-----------------------------------------------------
508 
509     /**
510      * Call once to initialize your Loggers for your test file.
511      * Also resets test name, result, case results, etc.
512      * <p>Currently, you must init/close your test file before init/closing
513      * any test cases.  No checking is currently done to ensure that
514      * mismatched test files are not nested.  This is an area that needs
515      * design decisions and some work eventually to be a really clean design.</p>
516      * <p>Not only do nested testfiles/testcases have implications for good
517      * testing practices, they may also have implications for various Loggers,
518      * especially XML or other ones with an implicit hierarcy in the reports.</p>
519      * @author Shane_Curcuru@lotus.com
520      * @param name file name or tag specifying the test.
521      * @param comment comment about the test.
522      */
testFileInit(String name, String comment)523     public void testFileInit(String name, String comment)
524     {
525 
526         testName = name;
527         testComment = comment;
528         testResult = DEFAULT_RESULT;
529         caseNum = 0;
530         caseComment = null;
531         caseResult = DEFAULT_RESULT;
532         duringTestCase = false;
533 
534         for (int i = 0; i < numLoggers; i++)
535         {
536             loggers[i].testFileInit(testName, testComment);
537         }
538 
539         // Log out time whole test script starts
540         // Note there is a slight delay while logPerfMsg calls all reporters
541         long t = System.currentTimeMillis();
542 
543         logPerfMsg(TEST_START, t, testName);
544     }
545 
546     /**
547      * Call once to close out your test and summate results.
548      * <p>will close an open testCase before closing the file.  May also
549      * force all Loggers closed if getCloseOnFileClose() (which may imply
550      * that no more output will be logged to file-based reporters)</p>
551      * @author Shane_Curcuru@lotus.com
552      * @todo make this settable as to how/where the resultsCounters get output
553      */
testFileClose()554     public void testFileClose()
555     {
556 
557         // Cache the time whole test script ends
558         long t = System.currentTimeMillis();
559 
560         if (duringTestCase)
561         {
562 
563             // Either user messed up (forgot to call testCaseClose) or something went wrong
564             logErrorMsg("WARNING! testFileClose when duringTestCase=true!");
565 
566             // Force call to testCaseClose()
567             testCaseClose();
568         }
569 
570         // Actually log the time the test script ends after closing any potentially open testcases
571         logPerfMsg(TEST_STOP, t, testName);
572 
573         // Increment our results counters
574         incrementResultCounter(FILES, testResult);
575 
576         // Print out an overall count of results by type
577         // @todo make this settable as to how/where the resultsCounters get output
578         logResultsCounters();
579 
580         // end this testfile - finish up any reporting we need to
581         for (int i = 0; i < numLoggers; i++)
582         {
583 
584             // Log we're done and then flush
585             loggers[i].testFileClose(testComment, resultToString(testResult));
586             loggers[i].flush();
587 
588             // Only close each reporter if asked to; this implies we're done
589             //  and can't perform any more logging ourselves (or our reporters)
590             if (getCloseOnFileClose())
591             {
592                 loggers[i].close();
593             }
594         }
595 
596         // Note: explicitly leave testResult, caseResult, etc. set for debugging
597         //       purposes or for use by external test harnesses
598     }
599 
600     /**
601      * Implement Logger-only method.
602      * <p>Here, a Reporter is simply acting as a logger: so don't
603      * summate any results, do performance measuring, or anything
604      * else, just pass the call through to our Loggers.
605      * @param msg message to log out
606      * @param result result of testfile
607      */
testFileClose(String msg, String result)608     public void testFileClose(String msg, String result)
609     {
610 
611         if (duringTestCase)
612         {
613 
614             // Either user messed up (forgot to call testCaseClose) or something went wrong
615             logErrorMsg("WARNING! testFileClose when duringTestCase=true!");
616 
617             // Force call to testCaseClose()
618             testCaseClose();
619         }
620 
621         // end this testfile - finish up any reporting we need to
622         for (int i = 0; i < numLoggers; i++)
623         {
624 
625             // Log we're done and then flush
626             loggers[i].testFileClose(testComment, resultToString(testResult));
627             loggers[i].flush();
628 
629             // Only close each reporter if asked to; this implies we're done
630             //  and can't perform any more logging ourselves (or our reporters)
631             if (getCloseOnFileClose())
632             {
633                 loggers[i].close();
634             }
635         }
636     }
637 
638     /**
639      * Call once to start each test case; logs out testcase number and your comment.
640      * <p>Testcase numbers are calculated as integers incrementing from 1.  Will
641      * also close any previously init'd but not closed testcase.</p>
642      * @author Shane_Curcuru@lotus.com
643      * @todo investigate tieing this to the actual testCase methodnames,
644      * instead of blindly incrementing the counter
645      * @param comment short description of this test case's objective.
646      */
testCaseInit(String comment)647     public void testCaseInit(String comment)
648     {
649 
650         if (duringTestCase)
651         {
652 
653             // Either user messed up (forgot to call testCaseClose) or something went wrong
654             logErrorMsg("WARNING! testCaseInit when duringTestCase=true!");
655 
656             // Force call to testCaseClose()
657             testCaseClose();
658         }
659 
660         caseNum++;
661 
662         caseComment = comment;
663         caseResult = DEFAULT_RESULT;
664 
665         for (int i = 0; i < numLoggers; i++)
666         {
667             loggers[i].testCaseInit(String.valueOf(caseNum) + " "
668                                     + caseComment);
669         }
670 
671         duringTestCase = true;
672 
673         // Note there is a slight delay while logPerfMsg calls all reporters
674         long t = System.currentTimeMillis();
675 
676         logPerfMsg(CASE_START, t, caseComment);
677     }
678 
679     /**
680      * Call once to end each test case and sub-summate results.
681      * @author Shane_Curcuru@lotus.com
682      */
testCaseClose()683     public void testCaseClose()
684     {
685 
686         long t = System.currentTimeMillis();
687 
688         logPerfMsg(CASE_STOP, t, caseComment);
689 
690         if (!duringTestCase)
691         {
692             logErrorMsg("WARNING! testCaseClose when duringTestCase=false!");
693 
694             // Force call to testCaseInit()
695             // NEEDSWORK: should we really do this?  This ensures any results
696             //            are well-formed, however a user might not expect this.
697             testCaseInit("WARNING! testCaseClose when duringTestCase=false!");
698         }
699 
700         duringTestCase = false;
701         testResult = java.lang.Math.max(testResult, caseResult);
702 
703         // Increment our results counters
704         incrementResultCounter(CASES, caseResult);
705 
706         for (int i = 0; i < numLoggers; i++)
707         {
708             loggers[i].testCaseClose(
709                 String.valueOf(caseNum) + " " + caseComment,
710                 resultToString(caseResult));
711         }
712     }
713 
714     /**
715      * Implement Logger-only method.
716      * <p>Here, a Reporter is simply acting as a logger: so don't
717      * summate any results, do performance measuring, or anything
718      * else, just pass the call through to our Loggers.
719      * @param msg message of name of test case to log out
720      * @param result result of testfile
721      */
testCaseClose(String msg, String result)722     public void testCaseClose(String msg, String result)
723     {
724 
725         if (!duringTestCase)
726         {
727             logErrorMsg("WARNING! testCaseClose when duringTestCase=false!");
728 
729             // Force call to testCaseInit()
730             // NEEDSWORK: should we really do this?  This ensures any results
731             //            are well-formed, however a user might not expect this.
732             testCaseInit("WARNING! testCaseClose when duringTestCase=false!");
733         }
734 
735         duringTestCase = false;
736 
737         for (int i = 0; i < numLoggers; i++)
738         {
739             loggers[i].testCaseClose(
740                 String.valueOf(caseNum) + " " + caseComment,
741                 resultToString(caseResult));
742         }
743     }
744 
745     /**
746      * Calls back into a Test to run test cases in order.
747      * <p>Use reflection to call back and execute each testCaseXX method
748      * in the calling test in order, catching exceptions along the way.</p>
749      * //@todo rename to 'executeTestCases' or something
750      * //@todo implement options: either an inclusion or exclusion list
751      * @author Shane Curcuru
752      * @param testObject the test object itself.
753      * @param numTestCases number of consecutively numbered test cases to execute.
754      * @param options (future use: options to pass to testcases)
755      * @return status, true if OK, false if big bad error occoured
756      */
executeTests(Test testObject, int numTestCases, Object options)757     public boolean executeTests(Test testObject, int numTestCases,
758                                 Object options)
759     {
760 
761         // Flag denoting if we've had any errors
762         boolean gotException = false;
763 
764         // Declare all needed java variables
765         String tmpErrString = "executeTests: no errors yet";
766         Object noArgs[] = new Object[0];  // use options instead
767         Class noParams[] = new Class[0];
768         Method currTestCase;
769         Class testClass;
770 
771         // Get class reference for the test applet itself
772         testClass = testObject.getClass();
773 
774         logTraceMsg("executeTests: running " + numTestCases + " tests now.");
775 
776         for (int tcNum = 1; tcNum <= numTestCases; tcNum++)
777         {
778             try
779             {  // get a reference to the next test case that we'll be calling
780                 tmpErrString = "executeTests: No such method: testCase"
781                                + tcNum + "()";
782                 currTestCase = testClass.getMethod("testCase" + tcNum,
783                                                    noParams);
784 
785                 // Now directly invoke that test case
786                 tmpErrString =
787                     "executeTests: Method threw an exception: testCase"
788                     + tcNum + "(): ";
789 
790                 logTraceMsg("executeTests: invoking testCase" + tcNum
791                             + " now.");
792                 currTestCase.invoke(testObject, noArgs);
793             }
794             catch (InvocationTargetException ite)
795             {
796                 // Catch any error, log it as an error, and allow next test case to run
797                 gotException = true;
798                 testResult = java.lang.Math.max(ERRR_RESULT, testResult);
799                 tmpErrString += ite.toString();
800                 logErrorMsg(tmpErrString);
801 
802                 // Grab the contained error, log it if available
803                 java.lang.Throwable containedThrowable =
804                     ite.getTargetException();
805                 if (containedThrowable != null)
806                 {
807                     logThrowable(ERRORMSG, containedThrowable, tmpErrString + "(1)");
808                 }
809                 logThrowable(ERRORMSG, ite, tmpErrString + "(2)");
810             }  // end of catch
811             catch (Throwable t)
812             {
813                 // Catch any error, log it as an error, and allow next test case to run
814                 gotException = true;
815                 testResult = java.lang.Math.max(ERRR_RESULT, testResult);
816                 tmpErrString += t.toString();
817                 logErrorMsg(tmpErrString);
818                 logThrowable(ERRORMSG, t, tmpErrString);
819             }  // end of catch
820         }  // end of for
821 
822         // Convenience functionality: remind user if they appear to
823         //  have set numTestCases too low
824         try
825         {
826             // Get a reference to the *next* test case after numTestCases
827             int moreTestCase = numTestCases + 1;
828             currTestCase = testClass.getMethod("testCase" + moreTestCase, noParams);
829 
830             // If we get here, we found another testCase - warn the user
831             logWarningMsg("executeTests: extra testCase"+ moreTestCase
832                           + " found, perhaps numTestCases is too low?");
833         }
834         catch (Throwable t)
835         {
836             // Ignore errors: we don't care, since they didn't
837             //  ask us to look for this method anyway
838         }
839 
840         // Return true only if everything passed
841         if (testResult == PASS_RESULT)
842             return true;
843         else
844             return false;
845     }  // end of executeTests
846 
847     //-----------------------------------------------------
848     //-------- Test results logging routines --------
849     //-----------------------------------------------------
850 
851     /**
852      * Accessor for loggingLevel, determines what level of log*() calls get output.
853      * @return loggingLevel, as an int.
854      */
getLoggingLevel()855     public int getLoggingLevel()
856     {
857         return loggingLevel;
858     }
859 
860     /**
861      * Accessor for loggingLevel, determines what level of log*() calls get output.
862      * @param setLL loggingLevel; normalized to be between CRITICALMSG and TRACEMSG.
863      */
setLoggingLevel(int setLL)864     public void setLoggingLevel(int setLL)
865     {
866 
867         if (setLL < CRITICALMSG)
868         {
869             loggingLevel = CRITICALMSG;
870         }
871         else if (setLL > TRACEMSG)
872         {
873             loggingLevel = TRACEMSG;
874         }
875         else
876         {
877             loggingLevel = setLL;
878         }
879     }
880 
881     /**
882      * Report a comment to result file with specified severity.
883      * <p>Works in conjunction with {@link #loggingLevel };
884      * only outputs messages that are more severe (i.e. lower)
885      * than the current logging level.</p>
886      * <p>Note that some Loggers may limit the comment string,
887      * either in overall length or by stripping any linefeeds, etc.
888      * This is to allow for optimization of file or database-type
889      * reporters with fixed fields.  Users who need to log out
890      * special string data should use logArbitrary() instead.</p>
891      * <p>Remember, use {@link #check(String, String, String)
892      * various check*() methods} to report the actual results
893      * of your tests.</p>
894      * @author Shane_Curcuru@lotus.com
895      * @param level severity of message.
896      * @param msg comment to log out.
897      * @see #loggingLevel
898      */
logMsg(int level, String msg)899     public void logMsg(int level, String msg)
900     {
901 
902         if (level > loggingLevel)
903             return;
904 
905         for (int i = 0; i < numLoggers; i++)
906         {
907             loggers[i].logMsg(level, msg);
908         }
909     }
910 
911     /**
912      * Report an arbitrary String to result file with specified severity.
913      * Log out the String provided exactly as-is.
914      * @author Shane_Curcuru@lotus.com
915      * @param level severity or class of message.
916      * @param msg arbitrary String to log out.
917      */
logArbitrary(int level, String msg)918     public void logArbitrary(int level, String msg)
919     {
920 
921         if (level > loggingLevel)
922             return;
923 
924         for (int i = 0; i < numLoggers; i++)
925         {
926             loggers[i].logArbitrary(level, msg);
927         }
928     }
929 
930     /**
931      * Logs out statistics to result file with specified severity.
932      * <p>This is a general-purpose way to log out numeric statistics.  We accept
933      * both a long and a double to allow users to save whatever kind of numbers
934      * they need to, with the simplest API.  The actual meanings of the numbers
935      * are dependent on the implementer.</p>
936      * @author Shane_Curcuru@lotus.com
937      * @param level severity of message.
938      * @param lVal statistic in long format.
939      * @param dVal statistic in doubleformat.
940      * @param msg comment to log out.
941      */
logStatistic(int level, long lVal, double dVal, String msg)942     public void logStatistic(int level, long lVal, double dVal, String msg)
943     {
944 
945         if (level > loggingLevel)
946             return;
947 
948         for (int i = 0; i < numLoggers; i++)
949         {
950             loggers[i].logStatistic(level, lVal, dVal, msg);
951         }
952     }
953 
954     /**
955      * Logs out a element to results with specified severity.
956      * This method is primarily for reporters that output to fixed
957      * structures, like files, XML data, or databases.
958      * @author Shane_Curcuru@lotus.com
959      * @param level severity of message.
960      * @param element name of enclosing element
961      * @param attrs hash of name=value attributes
962      * @param msg Object to log out; up to reporters to handle
963      * processing of this; usually logs just .toString().
964      */
logElement(int level, String element, Hashtable attrs, Object msg)965     public void logElement(int level, String element, Hashtable attrs,
966                            Object msg)
967     {
968 
969         if (level > loggingLevel)
970             return;
971 
972         for (int i = 0; i < numLoggers; i++)
973         {
974             loggers[i].logElement(level, element, attrs, msg);
975         }
976     }
977 
978     /**
979      * Logs out Throwable.toString() and a stack trace of the
980      * Throwable with the specified severity.
981      * <p>Works in conjuntion with {@link #setLoggingLevel(int)};
982      * only outputs messages that are more severe than the current
983      * logging level.</p>
984      * <p>This uses logArbitrary to log out your msg - message,
985      * a newline, throwable.toString(), a newline,
986      * and then throwable.printStackTrace().</p>
987      * <p>Note that this does not imply a failure or problem in
988      * a test in any way: many tests may want to verify that
989      * certain exceptions are thrown, etc.</p>
990      * @author Shane_Curcuru@lotus.com
991      * @param level severity of message.
992      * @param throwable throwable/exception to log out.
993      * @param msg description of the throwable.
994      */
logThrowable(int level, Throwable throwable, String msg)995     public void logThrowable(int level, Throwable throwable, String msg)
996     {
997 
998         if (level > loggingLevel)
999             return;
1000 
1001         for (int i = 0; i < numLoggers; i++)
1002         {
1003             loggers[i].logThrowable(level, throwable, msg);
1004         }
1005     }
1006 
1007     /**
1008      * Logs out contents of a Hashtable with specified severity.
1009      * <p>Works in conjuntion with setLoggingLevel(int); only outputs messages that
1010      * are more severe than the current logging level.</p>
1011      * <p>Loggers should store or log the full contents of the hashtable.</p>
1012      * @author Shane_Curcuru@lotus.com
1013      * @param level severity of message.
1014      * @param hash Hashtable to log the contents of.
1015      * @param msg description of the Hashtable.
1016      */
logHashtable(int level, Hashtable hash, String msg)1017     public void logHashtable(int level, Hashtable hash, String msg)
1018     {
1019         if (level > loggingLevel)
1020           return;
1021 
1022         // Don't log anyway if level is 10 or less.
1023         //@todo revisit this decision: I don't like having special
1024         //  rules like this to exclude output.  On the other hand,
1025         //  if the user set loggingLevel this low, they really don't
1026         //  want much output coming out, and hashtables are big
1027         if (loggingLevel <= 10)
1028             return;
1029 
1030         for (int i = 0; i < numLoggers; i++)
1031         {
1032             loggers[i].logHashtable(level, hash, msg);
1033         }
1034     }
1035 
1036     /**
1037      * Logs out an critical a comment to results; always printed out.
1038      * @author Shane_Curcuru@lotus.com
1039      * @param msg comment to log out.
1040      */
logCriticalMsg(String msg)1041     public void logCriticalMsg(String msg)
1042     {
1043         logMsg(CRITICALMSG, msg);
1044     }
1045 
1046     // There is no logFailsOnlyMsg(String msg) method
1047 
1048     /**
1049      * Logs out an error a comment to results.
1050      * <p>Note that subclassed libraries may choose to override to
1051      * cause a fail to happen along with printing out the message.</p>
1052      * @author Shane_Curcuru@lotus.com
1053      * @param msg comment to log out.
1054      */
logErrorMsg(String msg)1055     public void logErrorMsg(String msg)
1056     {
1057         logMsg(ERRORMSG, msg);
1058     }
1059 
1060     /**
1061      * Logs out a warning a comment to results.
1062      * @author Shane_Curcuru@lotus.com
1063      * @param msg comment to log out.
1064      */
logWarningMsg(String msg)1065     public void logWarningMsg(String msg)
1066     {
1067         logMsg(WARNINGMSG, msg);
1068     }
1069 
1070     /**
1071      * Logs out an status a comment to results.
1072      * @author Shane_Curcuru@lotus.com
1073      * @param msg comment to log out.
1074      */
logStatusMsg(String msg)1075     public void logStatusMsg(String msg)
1076     {
1077         logMsg(STATUSMSG, msg);
1078     }
1079 
1080     /**
1081      * Logs out an informational a comment to results.
1082      * @author Shane_Curcuru@lotus.com
1083      * @param msg comment to log out.
1084      */
logInfoMsg(String msg)1085     public void logInfoMsg(String msg)
1086     {
1087         logMsg(INFOMSG, msg);
1088     }
1089 
1090     /**
1091      * Logs out an trace a comment to results.
1092      * @author Shane_Curcuru@lotus.com
1093      * @param msg comment to log out.
1094      */
logTraceMsg(String msg)1095     public void logTraceMsg(String msg)
1096     {
1097         logMsg(TRACEMSG, msg);
1098     }
1099 
1100     //-----------------------------------------------------
1101     //-------- Test results reporting check* routines --------
1102     //-----------------------------------------------------
1103     // There is no public void checkIncp(String comment) method
1104 
1105     /* EXPERIMENTAL: have duplicate set of check*() methods
1106        that all output some form of ID as well as comment.
1107        Leave the non-ID taking forms for both simplicity to the
1108        end user who doesn't care about IDs as well as for
1109        backwards compatibility.
1110     */
1111 
1112     /**
1113      * Writes out a Pass record with comment.
1114      * @author Shane_Curcuru@lotus.com
1115      * @param comment comment to log with the pass record.
1116      */
checkPass(String comment)1117     public void checkPass(String comment)
1118     {
1119         checkPass(comment, null);
1120     }
1121 
1122     /**
1123      * Writes out an ambiguous record with comment.
1124      * @author Shane_Curcuru@lotus.com
1125      * @param comment to log with the ambg record.
1126      */
checkAmbiguous(String comment)1127     public void checkAmbiguous(String comment)
1128     {
1129         checkAmbiguous(comment, null);
1130     }
1131 
1132     /**
1133      * Writes out a Fail record with comment.
1134      * @author Shane_Curcuru@lotus.com
1135      * @param comment comment to log with the fail record.
1136      */
checkFail(String comment)1137     public void checkFail(String comment)
1138     {
1139         checkFail(comment, null);
1140     }
1141 
1142 
1143     /**
1144      * Writes out an Error record with comment.
1145      * @author Shane_Curcuru@lotus.com
1146      * @param comment comment to log with the error record.
1147      */
checkErr(String comment)1148     public void checkErr(String comment)
1149     {
1150         checkErr(comment, null);
1151     }
1152 
1153     /**
1154      * Writes out a Pass record with comment.
1155      * A Pass signifies that an individual test point has completed and has
1156      * been verified to have behaved correctly.
1157      * <p>If you need to do your own specific comparisons, you can
1158      * do them in your code and then just call checkPass or checkFail.</p>
1159      * <p>Derived classes must implement this to <B>both</B> report the
1160      * results out appropriately <B>and</B> to summate the results, if needed.</p>
1161      * <p>Pass results are a low priority, except for INCP (incomplete).  Note
1162      * that if a test never calls check*(), it will have an incomplete result.</p>
1163      * @author Shane_Curcuru@lotus.com
1164      * @param comment to log with the pass record.
1165      * @param ID token to log with the pass record.
1166      */
checkPass(String comment, String id)1167     public void checkPass(String comment, String id)
1168     {
1169 
1170         // Increment our results counters
1171         incrementResultCounter(CHECKS, PASS_RESULT);
1172 
1173         // Special: only report it actually if needed
1174         if (getLoggingLevel() > FAILSONLY)
1175         {
1176             for (int i = 0; i < numLoggers; i++)
1177             {
1178                 loggers[i].checkPass(comment, id);
1179             }
1180         }
1181 
1182         caseResult = java.lang.Math.max(PASS_RESULT, caseResult);
1183     }
1184 
1185     /**
1186      * Writes out an ambiguous record with comment.
1187      * <p>Ambiguous results are neither pass nor fail. Different test
1188      * libraries may have slightly different reasons for using ambg.</p>
1189      * <p>Derived classes must implement this to <B>both</B> report the
1190      * results out appropriately <B>and</B> to summate the results, if needed.</p>
1191      * <p>Ambg results have a middling priority, and take precedence over incomplete and pass.</p>
1192      * <p>An Ambiguous result may signify that the test point has completed and either
1193      * appears to have succeded, or that it has produced a result but there is no known
1194      * 'gold' result to compare it to.</p>
1195      * @author Shane_Curcuru@lotus.com
1196      * @param comment to log with the ambg record.
1197      * @param ID token to log with the pass record.
1198      */
checkAmbiguous(String comment, String id)1199     public void checkAmbiguous(String comment, String id)
1200     {
1201 
1202         // Increment our results counters
1203         incrementResultCounter(CHECKS, AMBG_RESULT);
1204 
1205         for (int i = 0; i < numLoggers; i++)
1206         {
1207             loggers[i].checkAmbiguous(comment, id);
1208         }
1209 
1210         caseResult = java.lang.Math.max(AMBG_RESULT, caseResult);
1211     }
1212 
1213     /**
1214      * Writes out a Fail record with comment.
1215      * <p>If you need to do your own specific comparisons, you can
1216      * do them in your code and then just call checkPass or checkFail.</p>
1217      * <p>Derived classes must implement this to <B>both</B> report the
1218      * results out appropriately <B>and</B> to summate the results, if needed.</p>
1219      * <p>Fail results have a high priority, and take precedence over incomplete, pass, and ambiguous.</p>
1220      * <p>A Fail signifies that an individual test point has completed and has
1221      * been verified to have behaved <B>in</B>correctly.</p>
1222      * @author Shane_Curcuru@lotus.com
1223      * @param comment to log with the fail record.
1224      * @param ID token to log with the pass record.
1225      */
checkFail(String comment, String id)1226     public void checkFail(String comment, String id)
1227     {
1228 
1229         // Increment our results counters
1230         incrementResultCounter(CHECKS, FAIL_RESULT);
1231 
1232         for (int i = 0; i < numLoggers; i++)
1233         {
1234             loggers[i].checkFail(comment, id);
1235         }
1236 
1237         caseResult = java.lang.Math.max(FAIL_RESULT, caseResult);
1238     }
1239 
1240     /**
1241      * Writes out an Error record with comment.
1242      * <p>Derived classes must implement this to <B>both</B> report the
1243      * results out appropriately <B>and</B> to summate the results, if needed.</p>
1244      * <p>Error results have the highest priority, and take precedence over
1245      * all other results.</p>
1246      * <p>An Error signifies that something unusual has gone wrong with the execution
1247      * of the test at this point - likely something that will require a human to
1248      * debug to see what really happened.</p>
1249      * @author Shane_Curcuru@lotus.com
1250      * @param comment to log with the error record.
1251      * @param ID token to log with the pass record.
1252      */
checkErr(String comment, String id)1253     public void checkErr(String comment, String id)
1254     {
1255 
1256         // Increment our results counters
1257         incrementResultCounter(CHECKS, ERRR_RESULT);
1258 
1259         for (int i = 0; i < numLoggers; i++)
1260         {
1261             loggers[i].checkErr(comment, id);
1262         }
1263 
1264         caseResult = java.lang.Math.max(ERRR_RESULT, caseResult);
1265     }
1266 
1267     //-----------------------------------------------------
1268     //-------- Simplified Performance Logging - beyond interface Reporter --------
1269     //-----------------------------------------------------
1270 
1271     /** NEEDSDOC Field DEFAULT_PERFLOGGING_LEVEL          */
1272     protected final boolean DEFAULT_PERFLOGGING_LEVEL = false;
1273 
1274     /**
1275      * This determines if performance information is logged out to results.
1276      * <p>When true, extra performance records are written out to result files.</p>
1277      * @see #logPerfMsg(java.lang.String, long, java.lang.String)
1278      */
1279     protected boolean perfLogging = DEFAULT_PERFLOGGING_LEVEL;
1280 
1281     /**
1282      * Accessor for perfLogging, determines if we log performance info.
1283      * @todo add PerfLogging to Reporter interface
1284      * @return Whether or not we log performance info.
1285      */
getPerfLogging()1286     public boolean getPerfLogging()
1287     {
1288         return (perfLogging);
1289     }
1290 
1291     /**
1292      * Accessor for perfLogging, determines if we log performance info.
1293      * @param Whether or not we log performance info.
1294      *
1295      * NEEDSDOC @param setPL
1296      */
setPerfLogging(boolean setPL)1297     public void setPerfLogging(boolean setPL)
1298     {
1299         perfLogging = setPL;
1300     }
1301 
1302     /**
1303      * Constants used to mark performance records in output.
1304      */
1305 
1306     // Note: string representations are explicitly set to all be
1307     //       4 characters long to make it simpler to parse results
1308     public static final String TEST_START = "TSrt";
1309 
1310     /** NEEDSDOC Field TEST_STOP          */
1311     public static final String TEST_STOP = "TStp";
1312 
1313     /** NEEDSDOC Field CASE_START          */
1314     public static final String CASE_START = "CSrt";
1315 
1316     /** NEEDSDOC Field CASE_STOP          */
1317     public static final String CASE_STOP = "CStp";
1318 
1319     /** NEEDSDOC Field USER_TIMER          */
1320     public static final String USER_TIMER = "UTmr";
1321 
1322     /** NEEDSDOC Field USER_TIMESTAMP          */
1323     public static final String USER_TIMESTAMP = "UTim";
1324 
1325     /** NEEDSDOC Field USER_MEMORY          */
1326     public static final String USER_MEMORY = "UMem";
1327 
1328     /** NEEDSDOC Field PERF_SEPARATOR          */
1329     public static final String PERF_SEPARATOR = ";";
1330 
1331     /**
1332      * Logs out a performance statistic.
1333      * <p>Only logs times if perfLogging set to true.</p>
1334      * <p>As an optimization for record-based Loggers, this is a rather simplistic
1335      * way to log performance info - however it's sufficient for most purposes.</p>
1336      * @author Frank Bell
1337      * @param type type of performance statistic.
1338      * @param data long value of performance statistic.
1339      * @param msg comment to log out.
1340      */
logPerfMsg(String type, long data, String msg)1341     public void logPerfMsg(String type, long data, String msg)
1342     {
1343 
1344         if (getPerfLogging())
1345         {
1346             double dummy = 0;
1347 
1348             for (int i = 0; i < numLoggers; i++)
1349             {
1350 
1351                 // NEEDSWORK: simply put it at the current loggingLevel we have set
1352                 //            Is there a better way to mesh performance output with the rest?
1353                 loggers[i].logStatistic(loggingLevel, data, dummy,
1354                                         type + PERF_SEPARATOR + msg);
1355             }
1356         }
1357     }
1358 
1359     /**
1360      * Captures current time in milliseconds, only if perfLogging.
1361      * @author Shane_Curcuru@lotus.com
1362      * @param msg comment to log out.
1363      */
1364     protected Hashtable perfTimers = new Hashtable();
1365 
1366     /**
1367      * NEEDSDOC Method startTimer
1368      *
1369      *
1370      * NEEDSDOC @param msg
1371      */
startTimer(String msg)1372     public void startTimer(String msg)
1373     {
1374 
1375         // Note optimization: only capture times if perfLogging
1376         if ((perfLogging) && (msg != null))
1377         {
1378             perfTimers.put(msg, new Long(System.currentTimeMillis()));
1379         }
1380     }
1381 
1382     /**
1383      * Captures current time in milliseconds and logs out difference.
1384      * Will only log times if perfLogging set to true.
1385      * <p>Only logs time if it finds a corresponding msg entry that was startTimer'd.</p>
1386      * @author Shane_Curcuru@lotus.com
1387      * @param msg comment to log out.
1388      */
stopTimer(String msg)1389     public void stopTimer(String msg)
1390     {
1391 
1392         // Capture time immediately to reduce latency
1393         long stopTime = System.currentTimeMillis();
1394 
1395         // Note optimization: only use times if perfLogging
1396         if ((perfLogging) && (msg != null))
1397         {
1398             Long startTime = (Long) perfTimers.get(msg);
1399 
1400             logPerfMsg(USER_TIMER, (stopTime - startTime.longValue()), msg);
1401             perfTimers.remove(msg);
1402         }
1403     }
1404 
1405     /**
1406      * Accessor for currently running test case number, read-only.
1407      * @return current test case number.
1408      */
getCurrentCaseNum()1409     public int getCurrentCaseNum()
1410     {
1411         return caseNum;
1412     }
1413 
1414     /**
1415      * Accessor for current test case's result, read-only.
1416      * @return current test case result.
1417      */
getCurrentCaseResult()1418     public int getCurrentCaseResult()
1419     {
1420         return caseResult;
1421     }
1422 
1423     /**
1424      * Accessor for current test case's description, read-only.
1425      * @return current test case result.
1426      */
getCurrentCaseComment()1427     public String getCurrentCaseComment()
1428     {
1429         return caseComment;
1430     }
1431 
1432     /**
1433      * Accessor for overall test file result, read-only.
1434      * @return test file's overall result.
1435      */
getCurrentFileResult()1436     public int getCurrentFileResult()
1437     {
1438         return testResult;
1439     }
1440 
1441     /**
1442      * Utility method to log out overall result counters.
1443      *
1444      * @param count number of this kind of result
1445      * @param desc description of this kind of result
1446      */
logResultsCounter(int count, String desc)1447     protected void logResultsCounter(int count, String desc)
1448     {
1449 
1450         // Optimization: Only log the kinds of results we have
1451         if (count > 0)
1452             logStatistic(loggingLevel, count, 0, desc);
1453     }
1454 
1455     /** Utility method to log out overall result counters. */
logResultsCounters()1456     public void logResultsCounters()
1457     {
1458 
1459         // NEEDSWORK: what's the best format to display this stuff in?
1460         // NEEDSWORK: what loggingLevel should we use?
1461         // NEEDSWORK: temporarily skipping the 'files' since
1462         //            we only have tests with one file being run
1463         // logResultsCounter(incpCount[FILES], "incpCount[FILES]");
1464         logResultsCounter(incpCount[CASES], "incpCount[CASES]");
1465         logResultsCounter(incpCount[CHECKS], "incpCount[CHECKS]");
1466 
1467         // logResultsCounter(passCount[FILES], "passCount[FILES]");
1468         logResultsCounter(passCount[CASES], "passCount[CASES]");
1469         logResultsCounter(passCount[CHECKS], "passCount[CHECKS]");
1470 
1471         // logResultsCounter(ambgCount[FILES], "ambgCount[FILES]");
1472         logResultsCounter(ambgCount[CASES], "ambgCount[CASES]");
1473         logResultsCounter(ambgCount[CHECKS], "ambgCount[CHECKS]");
1474 
1475         // logResultsCounter(failCount[FILES], "failCount[FILES]");
1476         logResultsCounter(failCount[CASES], "failCount[CASES]");
1477         logResultsCounter(failCount[CHECKS], "failCount[CHECKS]");
1478 
1479         // logResultsCounter(errrCount[FILES], "errrCount[FILES]");
1480         logResultsCounter(errrCount[CASES], "errrCount[CASES]");
1481         logResultsCounter(errrCount[CHECKS], "errrCount[CHECKS]");
1482     }
1483 
1484     /**
1485      * Utility method to store overall result counters.
1486      *
1487      * @return a Hashtable of various results items suitable for
1488      * passing to logElement as attrs
1489      */
createResultsStatusHash()1490     protected Hashtable createResultsStatusHash()
1491     {
1492         Hashtable resHash = new Hashtable();
1493         if (incpCount[CASES] > 0)
1494             resHash.put(INCP + "-cases", new Integer(incpCount[CASES]));
1495         if (incpCount[CHECKS] > 0)
1496             resHash.put(INCP + "-checks", new Integer(incpCount[CHECKS]));
1497 
1498         if (passCount[CASES] > 0)
1499             resHash.put(PASS + "-cases", new Integer(passCount[CASES]));
1500         if (passCount[CHECKS] > 0)
1501             resHash.put(PASS + "-checks", new Integer(passCount[CHECKS]));
1502 
1503         if (ambgCount[CASES] > 0)
1504             resHash.put(AMBG + "-cases", new Integer(ambgCount[CASES]));
1505         if (ambgCount[CHECKS] > 0)
1506             resHash.put(AMBG + "-checks", new Integer(ambgCount[CHECKS]));
1507 
1508         if (failCount[CASES] > 0)
1509             resHash.put(FAIL + "-cases", new Integer(failCount[CASES]));
1510         if (failCount[CHECKS] > 0)
1511             resHash.put(FAIL + "-checks", new Integer(failCount[CHECKS]));
1512 
1513         if (errrCount[CASES] > 0)
1514             resHash.put(ERRR + "-cases", new Integer(errrCount[CASES]));
1515         if (errrCount[CHECKS] > 0)
1516             resHash.put(ERRR + "-checks", new Integer(errrCount[CHECKS]));
1517         return resHash;
1518     }
1519 
1520     /**
1521      * Utility method to write out overall result counters.
1522      *
1523      * <p>This writes out both a testsummary element as well as
1524      * writing a separate marker file for the test's currently
1525      * rolled-up test results.</p>
1526      *
1527      * <p>Note if writeFile is true, we do a bunch of additional
1528      * processing, including deleting any potential marker
1529      * files, along with creating a new marker file.  This section
1530      * of code explicitly does file creation and also includes
1531      * some basic XML-isms in it.</p>
1532      *
1533      * <p>Marker files look like: [testStat][testName].xml, where
1534      * testStat is the actual current status, like
1535      * Pass/Fail/Ambg/Errr/Incp, and testName comes from the
1536      * currently executing test; this may be overridden by
1537 		  * setting OPT_SUMMARYFILE.</p>
1538      *
1539      * @param writeFile if we should also write out a separate
1540      * Passname/Failname marker file as well
1541      */
writeResultsStatus(boolean writeFile)1542     public void writeResultsStatus(boolean writeFile)
1543     {
1544         final String DEFAULT_SUMMARY_NAME = "ResultsSummary.xml";
1545         Hashtable resultsHash = createResultsStatusHash();
1546         resultsHash.put("desc", testComment);
1547         resultsHash.put("testName", testName);
1548         //@todo the actual path in the property below may not necessarily
1549         //  either exist or be the correct location vis-a-vis the file
1550         //  that we're writing out - but it should be close
1551         resultsHash.put(OPT_LOGFILE, reporterProps.getProperty(OPT_LOGFILE, DEFAULT_SUMMARY_NAME));
1552         try
1553         {
1554             resultsHash.put("baseref", System.getProperty("user.dir"));
1555         }
1556         catch (Exception e) { /* no-op, ignore */ }
1557 
1558         String elementName = "teststatus";
1559         String overallResult = resultToString(getCurrentFileResult());
1560         // Ask each of our loggers to report this
1561         for (int i = 0; i < numLoggers; i++)
1562         {
1563             loggers[i].logElement(CRITICALMSG, elementName, resultsHash, overallResult);
1564         }
1565 
1566         // Only continue if user asked us to
1567         if (!writeFile)
1568             return;
1569 
1570         // Now write an actual file out as a marker for enclosing
1571         //  harnesses and build environments
1572 
1573         // Calculate the name relative to any logfile we have
1574         String logFileBase = null;
1575         try
1576         {
1577             // CanonicalPath gives a better path, especially if
1578             //  you mix your path separators up
1579             logFileBase = (new File(reporterProps.getProperty(OPT_LOGFILE, DEFAULT_SUMMARY_NAME))).getCanonicalPath();
1580         }
1581         catch (IOException ioe)
1582         {
1583             logFileBase = (new File(reporterProps.getProperty(OPT_LOGFILE, DEFAULT_SUMMARY_NAME))).getAbsolutePath();
1584         }
1585         logFileBase = (new File(logFileBase)).getParent();
1586 		 		 // Either use the testName or an optionally set summary name
1587 		 		 String summaryFileBase = reporterProps.getProperty(OPT_SUMMARYFILE, testName + ".xml");
1588         final File[] summaryFiles =
1589         {
1590             // Note array is ordered; should be re-designed so this doesn't matter
1591             // Coordinate PASS name with results.marker in build.xml
1592             // File name rationale: put Pass/Fail/etc first, so they
1593             //  all show up together in dir listing; include
1594             //  testName so you know where it came from; make it
1595             //  .xml since it is an XML file
1596             new File(logFileBase, INCP + "-" + summaryFileBase),
1597             new File(logFileBase, PASS + "-" + summaryFileBase),
1598             new File(logFileBase, AMBG + "-" + summaryFileBase),
1599             new File(logFileBase, FAIL + "-" + summaryFileBase),
1600             new File(logFileBase, ERRR + "-" + summaryFileBase)
1601         };
1602         // Clean up any pre-existing files that might be confused
1603         //  as markers from this testrun
1604         for (int i = 0; i < summaryFiles.length; i++)
1605         {
1606             if (summaryFiles[i].exists())
1607                 summaryFiles[i].delete();
1608         }
1609 
1610         File summaryFile = null;
1611         switch (getCurrentFileResult())
1612         {
1613             case INCP_RESULT:
1614                 summaryFile = summaryFiles[0];
1615                 break;
1616             case PASS_RESULT:
1617                 summaryFile = summaryFiles[1];
1618                 break;
1619             case AMBG_RESULT:
1620                 summaryFile = summaryFiles[2];
1621                 break;
1622             case FAIL_RESULT:
1623                 summaryFile = summaryFiles[3];
1624                 break;
1625             case ERRR_RESULT:
1626                 summaryFile = summaryFiles[4];
1627                 break;
1628             default:
1629                 // Use error case, this should never happen
1630                 summaryFile = summaryFiles[4];
1631                 break;
1632         }
1633 		 		 resultsHash.put(OPT_SUMMARYFILE, summaryFile.getPath());
1634         // Now actually write out the summary file
1635         try
1636         {
1637             PrintWriter printWriter = new PrintWriter(new FileWriter(summaryFile));
1638             // Fake the output of Logger.logElement mostly; except
1639             //  we add an XML header so this is a legal XML doc
1640             printWriter.println("<?xml version=\"1.0\"?>");
1641             printWriter.println("<" + elementName);
1642             for (Enumeration keys = resultsHash.keys();
1643                     keys.hasMoreElements(); /* no increment portion */ )
1644             {
1645                 Object key = keys.nextElement();
1646                 printWriter.println(key + "=\"" + resultsHash.get(key) + "\"");
1647             }
1648             printWriter.println(">");
1649             printWriter.println(overallResult);
1650             printWriter.println("</" + elementName + ">");
1651             printWriter.close();
1652         }
1653         catch(Exception e)
1654         {
1655             logErrorMsg("writeResultsStatus: Can't write: " + summaryFile);
1656         }
1657     }
1658 
1659     //-----------------------------------------------------
1660     //-------- Test results reporting check* routines --------
1661     //-----------------------------------------------------
1662 
1663     /**
1664      * Compares actual and expected, and logs the result, pass/fail.
1665      * The comment you pass is added along with the pass/fail, of course.
1666      * Currenly, you may pass a pair of any of these simple {type}:
1667      * <ui>
1668      * <li>boolean</li>
1669      * <li>byte</li>
1670      * <li>short</li>
1671      * <li>int</li>
1672      * <li>long</li>
1673      * <li>float</li>
1674      * <li>double</li>
1675      * <li>String</li>
1676      * </ui>
1677      * <p>While tests could simply call checkPass(comment), providing these convenience
1678      * method can save lines of code, since you can replace:</p>
1679      * <code>if (foo = bar) <BR>
1680      *           checkPass(comment); <BR>
1681      *       else <BR>
1682      *           checkFail(comment);</code>
1683      * <p>With the much simpler:</p>
1684      * <code>check(foo, bar, comment);</code>
1685      * <p>Plus, you can either use or ignore the boolean return value.</p>
1686      * <p>Note that individual methods checkInt(...), checkLong(...), etc. also exist.
1687      * These type-independent overriden methods are provided as a convenience to
1688      * Java-only testwriters.  JavaScript scripts must call the
1689      * type-specific checkInt(...), checkString(...), etc. methods directly.</p>
1690      * <p>Note that testwriters are free to ignore the boolean return value.</p>
1691      * @author Shane_Curcuru@lotus.com
1692      * @param actual value returned from your test code.
1693      * @param expected value that test should return to pass.
1694      * @param comment to log out with result.
1695      * @return status, true=pass, false otherwise
1696      * @see #checkPass
1697      * @see #checkFail
1698      * @see #checkObject
1699      */
check(boolean actual, boolean expected, String comment)1700     public boolean check(boolean actual, boolean expected, String comment)
1701     {
1702         return (checkBool(actual, expected, comment));
1703     }
1704 
1705     /**
1706      * NEEDSDOC Method check
1707      *
1708      *
1709      * NEEDSDOC @param actual
1710      * NEEDSDOC @param expected
1711      * NEEDSDOC @param comment
1712      *
1713      * NEEDSDOC (check) @return
1714      */
check(byte actual, byte expected, String comment)1715     public boolean check(byte actual, byte expected, String comment)
1716     {
1717         return (checkByte(actual, expected, comment));
1718     }
1719 
1720     /**
1721      * NEEDSDOC Method check
1722      *
1723      *
1724      * NEEDSDOC @param actual
1725      * NEEDSDOC @param expected
1726      * NEEDSDOC @param comment
1727      *
1728      * NEEDSDOC (check) @return
1729      */
check(short actual, short expected, String comment)1730     public boolean check(short actual, short expected, String comment)
1731     {
1732         return (checkShort(actual, expected, comment));
1733     }
1734 
1735     /**
1736      * NEEDSDOC Method check
1737      *
1738      *
1739      * NEEDSDOC @param actual
1740      * NEEDSDOC @param expected
1741      * NEEDSDOC @param comment
1742      *
1743      * NEEDSDOC (check) @return
1744      */
check(int actual, int expected, String comment)1745     public boolean check(int actual, int expected, String comment)
1746     {
1747         return (checkInt(actual, expected, comment));
1748     }
1749 
1750     /**
1751      * NEEDSDOC Method check
1752      *
1753      *
1754      * NEEDSDOC @param actual
1755      * NEEDSDOC @param expected
1756      * NEEDSDOC @param comment
1757      *
1758      * NEEDSDOC (check) @return
1759      */
check(long actual, long expected, String comment)1760     public boolean check(long actual, long expected, String comment)
1761     {
1762         return (checkLong(actual, expected, comment));
1763     }
1764 
1765     /**
1766      * NEEDSDOC Method check
1767      *
1768      *
1769      * NEEDSDOC @param actual
1770      * NEEDSDOC @param expected
1771      * NEEDSDOC @param comment
1772      *
1773      * NEEDSDOC (check) @return
1774      */
check(float actual, float expected, String comment)1775     public boolean check(float actual, float expected, String comment)
1776     {
1777         return (checkFloat(actual, expected, comment));
1778     }
1779 
1780     /**
1781      * NEEDSDOC Method check
1782      *
1783      *
1784      * NEEDSDOC @param actual
1785      * NEEDSDOC @param expected
1786      * NEEDSDOC @param comment
1787      *
1788      * NEEDSDOC (check) @return
1789      */
check(double actual, double expected, String comment)1790     public boolean check(double actual, double expected, String comment)
1791     {
1792         return (checkDouble(actual, expected, comment));
1793     }
1794 
1795     /**
1796      * NEEDSDOC Method check
1797      *
1798      *
1799      * NEEDSDOC @param actual
1800      * NEEDSDOC @param expected
1801      * NEEDSDOC @param comment
1802      *
1803      * NEEDSDOC (check) @return
1804      */
check(String actual, String expected, String comment)1805     public boolean check(String actual, String expected, String comment)
1806     {
1807         return (checkString(actual, expected, comment));
1808     }
1809 
1810     // No check(Object, Object, String) currently provided, please call checkObject(...) directly
1811 
1812     /**
1813      * Compares actual and expected (Object), and logs the result, pass/fail.
1814      * <p><b>Special note for checkObject:</b></p>
1815      * <p>Since this takes an object reference and not a primitive type,
1816      * it works slightly differently than other check{Type} methods.</p>
1817      * <ui>
1818      * <li>If both are null, then Pass</li>
1819      * <li>Else If actual.equals(expected) than Pass</li>
1820      * <li>Else Fail</li>
1821      * </ui>
1822      * @author Shane_Curcuru@lotus.com
1823      * @param actual Object returned from your test code.
1824      * @param expected Object that test should return to pass.
1825      * @param comment to log out with result.
1826      * @see #checkPass
1827      * @see #checkFail
1828      * @see #check
1829      *
1830      * NEEDSDOC ($objectName$) @return
1831      */
checkObject(Object actual, Object expected, String comment)1832     public boolean checkObject(Object actual, Object expected, String comment)
1833     {
1834 
1835         // Pass if both null, or both valid & equals
1836         if (actual != null)
1837         {
1838             if (actual.equals(expected))
1839             {
1840                 checkPass(comment);
1841 
1842                 return true;
1843             }
1844             else
1845             {
1846                 checkFail(comment);
1847 
1848                 return false;
1849             }
1850         }
1851         else
1852         {  // actual is null, so can't use .equals
1853             if (expected == null)
1854             {
1855                 checkPass(comment);
1856 
1857                 return true;
1858             }
1859             else
1860             {
1861                 checkFail(comment);
1862 
1863                 return false;
1864             }
1865         }
1866     }
1867 
1868     /**
1869      * NEEDSDOC Method checkBool
1870      *
1871      *
1872      * NEEDSDOC @param actual
1873      * NEEDSDOC @param expected
1874      * NEEDSDOC @param comment
1875      *
1876      * NEEDSDOC (checkBool) @return
1877      */
checkBool(boolean actual, boolean expected, String comment)1878     public boolean checkBool(boolean actual, boolean expected, String comment)
1879     {
1880 
1881         if (actual == expected)
1882         {
1883             checkPass(comment);
1884 
1885             return true;
1886         }
1887         else
1888         {
1889             checkFail(comment);
1890 
1891             return false;
1892         }
1893     }
1894 
1895     /**
1896      * NEEDSDOC Method checkByte
1897      *
1898      *
1899      * NEEDSDOC @param actual
1900      * NEEDSDOC @param expected
1901      * NEEDSDOC @param comment
1902      *
1903      * NEEDSDOC (checkByte) @return
1904      */
checkByte(byte actual, byte expected, String comment)1905     public boolean checkByte(byte actual, byte expected, String comment)
1906     {
1907 
1908         if (actual == expected)
1909         {
1910             checkPass(comment);
1911 
1912             return true;
1913         }
1914         else
1915         {
1916             checkFail(comment);
1917 
1918             return false;
1919         }
1920     }
1921 
1922     /**
1923      * NEEDSDOC Method checkShort
1924      *
1925      *
1926      * NEEDSDOC @param actual
1927      * NEEDSDOC @param expected
1928      * NEEDSDOC @param comment
1929      *
1930      * NEEDSDOC (checkShort) @return
1931      */
checkShort(short actual, short expected, String comment)1932     public boolean checkShort(short actual, short expected, String comment)
1933     {
1934 
1935         if (actual == expected)
1936         {
1937             checkPass(comment);
1938 
1939             return true;
1940         }
1941         else
1942         {
1943             checkFail(comment);
1944 
1945             return false;
1946         }
1947     }
1948 
1949     /**
1950      * NEEDSDOC Method checkInt
1951      *
1952      *
1953      * NEEDSDOC @param actual
1954      * NEEDSDOC @param expected
1955      * NEEDSDOC @param comment
1956      *
1957      * NEEDSDOC (checkInt) @return
1958      */
checkInt(int actual, int expected, String comment)1959     public boolean checkInt(int actual, int expected, String comment)
1960     {
1961 
1962         if (actual == expected)
1963         {
1964             checkPass(comment);
1965 
1966             return true;
1967         }
1968         else
1969         {
1970             checkFail(comment);
1971 
1972             return false;
1973         }
1974     }
1975 
1976     /**
1977      * NEEDSDOC Method checkLong
1978      *
1979      *
1980      * NEEDSDOC @param actual
1981      * NEEDSDOC @param expected
1982      * NEEDSDOC @param comment
1983      *
1984      * NEEDSDOC (checkLong) @return
1985      */
checkLong(long actual, long expected, String comment)1986     public boolean checkLong(long actual, long expected, String comment)
1987     {
1988 
1989         if (actual == expected)
1990         {
1991             checkPass(comment);
1992 
1993             return true;
1994         }
1995         else
1996         {
1997             checkFail(comment);
1998 
1999             return false;
2000         }
2001     }
2002 
2003     /**
2004      * NEEDSDOC Method checkFloat
2005      *
2006      *
2007      * NEEDSDOC @param actual
2008      * NEEDSDOC @param expected
2009      * NEEDSDOC @param comment
2010      *
2011      * NEEDSDOC (checkFloat) @return
2012      */
checkFloat(float actual, float expected, String comment)2013     public boolean checkFloat(float actual, float expected, String comment)
2014     {
2015 
2016         if (actual == expected)
2017         {
2018             checkPass(comment);
2019 
2020             return true;
2021         }
2022         else
2023         {
2024             checkFail(comment);
2025 
2026             return false;
2027         }
2028     }
2029 
2030     /**
2031      * NEEDSDOC Method checkDouble
2032      *
2033      *
2034      * NEEDSDOC @param actual
2035      * NEEDSDOC @param expected
2036      * NEEDSDOC @param comment
2037      *
2038      * NEEDSDOC (checkDouble) @return
2039      */
checkDouble(double actual, double expected, String comment)2040     public boolean checkDouble(double actual, double expected, String comment)
2041     {
2042 
2043         if (actual == expected)
2044         {
2045             checkPass(comment);
2046 
2047             return true;
2048         }
2049         else
2050         {
2051             checkFail(comment);
2052 
2053             return false;
2054         }
2055     }
2056 
2057     /**
2058      * Compares actual and expected (String), and logs the result, pass/fail.
2059      * <p><b>Special note for checkString:</b></p>
2060      * <p>Since this takes a String object and not a primitive type,
2061      * it works slightly differently than other check{Type} methods.</p>
2062      * <ui>
2063      * <li>If both are null, then Pass</li>
2064      * <li>Else If actual.compareTo(expected) == 0 than Pass</li>
2065      * <li>Else Fail</li>
2066      * </ui>
2067      * @author Shane_Curcuru@lotus.com
2068      * @param actual String returned from your test code.
2069      * @param expected String that test should return to pass.
2070      * @param comment to log out with result.
2071      * @see #checkPass
2072      * @see #checkFail
2073      * @see #checkObject
2074      *
2075      * NEEDSDOC ($objectName$) @return
2076      */
checkString(String actual, String expected, String comment)2077     public boolean checkString(String actual, String expected, String comment)
2078     {
2079 
2080         // Pass if both null, or both valid & equals
2081         if (actual != null)
2082         {
2083 
2084             // .compareTo returns 0 if the strings match lexicographically
2085             if ((expected != null) && (actual.compareTo(expected) == 0))
2086             {
2087                 checkPass(comment);
2088 
2089                 return true;
2090             }
2091             else
2092             {
2093                 checkFail(comment);
2094 
2095                 return false;
2096             }
2097         }
2098         else
2099         {  // actual is null, so can't use .equals
2100             if (expected == null)
2101             {
2102                 checkPass(comment);
2103 
2104                 return true;
2105             }
2106             else
2107             {
2108                 checkFail(comment);
2109 
2110                 return false;
2111             }
2112         }
2113     }
2114 
2115     /**
2116      * Uses an external CheckService to Compares actual and expected,
2117      * and logs the result, pass/fail.
2118      * <p>CheckServices may be implemented to do custom equivalency
2119      * checking between complex object types. It is the responsibility
2120      * of the CheckService to call back into us to report results.</p>
2121      * @author Shane_Curcuru@lotus.com
2122      * @param CheckService implementation to use
2123      *
2124      * @param service a non-null CheckService implementation for
2125      * this type of actual and expected object
2126      * @param actual Object returned from your test code.
2127      * @param expected Object that test should return to pass.
2128      * @param comment to log out with result.
2129      * @return status true if PASS_RESULT, false otherwise
2130      * @see #checkPass
2131      * @see #checkFail
2132      * @see #check
2133      */
check(CheckService service, Object actual, Object expected, String comment)2134     public boolean check(CheckService service, Object actual,
2135                          Object expected, String comment)
2136     {
2137 
2138         if (service == null)
2139         {
2140             checkErr("CheckService null for: " + comment);
2141 
2142             return false;
2143         }
2144 
2145         if (service.check(this, actual, expected, comment) == PASS_RESULT)
2146             return true;
2147         else
2148             return false;
2149     }
2150 
2151     /**
2152      * Uses an external CheckService to Compares actual and expected,
2153      * and logs the result, pass/fail.
2154      */
check(CheckService service, Object actual, Object expected, String comment, String id)2155     public boolean check(CheckService service, Object actual,
2156                          Object expected, String comment, String id)
2157     {
2158 
2159         if (service == null)
2160         {
2161             checkErr("CheckService null for: " + comment);
2162 
2163             return false;
2164         }
2165 
2166         if (service.check(this, actual, expected, comment, id) == PASS_RESULT)
2167             return true;
2168         else
2169             return false;
2170     }
2171 
2172     /** Flag to control internal debugging of Reporter; sends extra info to System.out. */
2173     protected boolean debug = false;
2174 
2175     /**
2176      * Accessor for internal debugging flag.
2177      *
2178      * NEEDSDOC ($objectName$) @return
2179      */
getDebug()2180     public boolean getDebug()
2181     {
2182         return (debug);
2183     }
2184 
2185     /**
2186      * Accessor for internal debugging flag.
2187      *
2188      * NEEDSDOC @param setDbg
2189      */
setDebug(boolean setDbg)2190     public void setDebug(boolean setDbg)
2191     {
2192 
2193         debug = setDbg;
2194 
2195         debugPrintln("setDebug enabled");  // will only print if setDbg was true
2196     }
2197 
2198     /**
2199      * Basic debugging output wrapper for Reporter.
2200      *
2201      * NEEDSDOC @param msg
2202      */
debugPrintln(String msg)2203     public void debugPrintln(String msg)
2204     {
2205 
2206         if (!debug)
2207             return;
2208 
2209         // If we have reporters, use them
2210         if (numLoggers > 0)
2211             logCriticalMsg("RI.dP: " + msg);
2212 
2213             // Otherwise, just dump to the console
2214         else
2215             System.out.println("RI.dP: " + msg);
2216     }
2217 
2218     /**
2219      * Utility method to increment result counters.
2220      *
2221      * NEEDSDOC @param ctrOffset
2222      * NEEDSDOC @param r
2223      */
incrementResultCounter(int ctrOffset, int r)2224     public void incrementResultCounter(int ctrOffset, int r)
2225     {
2226 
2227         switch (r)
2228         {
2229         case INCP_RESULT :
2230             incpCount[ctrOffset]++;
2231             break;
2232         case PASS_RESULT :
2233             passCount[ctrOffset]++;
2234             break;
2235         case AMBG_RESULT :
2236             ambgCount[ctrOffset]++;
2237             break;
2238         case FAIL_RESULT :
2239             failCount[ctrOffset]++;
2240             break;
2241         case ERRR_RESULT :
2242             errrCount[ctrOffset]++;
2243             break;
2244         default :
2245             ;  // NEEDSWORK: should we report this, or allow users to add their own counters?
2246         }
2247     }
2248 
2249     /**
2250      * Utility method to translate an int result to a string.
2251      *
2252      * NEEDSDOC @param r
2253      *
2254      * NEEDSDOC ($objectName$) @return
2255      */
resultToString(int r)2256     public static String resultToString(int r)
2257     {
2258 
2259         switch (r)
2260         {
2261         case INCP_RESULT :
2262             return (INCP);
2263         case PASS_RESULT :
2264             return (PASS);
2265         case AMBG_RESULT :
2266             return (AMBG);
2267         case FAIL_RESULT :
2268             return (FAIL);
2269         case ERRR_RESULT :
2270             return (ERRR);
2271         default :
2272             return ("Unkn");  // NEEDSWORK: should have better constant for this
2273         }
2274     }
2275 }  // end of class Reporter
2276 
2277