blob: 34efd0694c6e779353e12a181796acb40133d18f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* $Id$
*/
/*
*
* Reporter.java
*
*/
package org.apache.qetest;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
/**
* Class defining how a test can report results including convenience methods.
* <p>Tests generally interact with a Reporter, which turns around to call
* a Logger to actually store the results. The Reporter serves as a
* single funnel for all results, hiding both the details and number of
* actual loggers that might currently be turned on (file, screen, network,
* etc.) from the test that created us.</p>
* <p>Note that Reporter adds numerous convenience methods that, while they
* are not strictly necessary to express a test's results, make coding
* tests much easier. Reporter is designed to be subclassed for your
* particular application; in general you only need to provide setup mechanisims
* specific to your testing/product environment.</p>
* @todo all methods should check that available loggers are OK
* @todo explain better how results are rolled up and calculated
* @author Shane_Curcuru@lotus.com
* @author Jo_Grant@lotus.com
* @version $Id$
*/
public class Reporter implements Logger
{
/**
* Parameter: (optional) Name of results summary file.
* <p>This is a custom parameter optionally used in writeResultsStatus.</p>
*/
public static final String OPT_SUMMARYFILE = "summaryFile";
/**
* Constructor calls initialize(p).
* @param p Properties block to initialize us with.
*/
public Reporter(Properties p)
{
ready = initialize(p);
}
/** If we're ready to start outputting yet. */
protected boolean ready = false;
//-----------------------------------------------------
//-------- Implement Logger Control and utility routines --------
//-----------------------------------------------------
/**
* Return a description of what this Logger/Reporter does.
* @author Shane_Curcuru@lotus.com
* @return description of how this Logger outputs results, OR
* how this Reporter uses Loggers, etc..
*/
public String getDescription()
{
return "Reporter: default reporter implementation";
}
/**
* Returns information about the Property name=value pairs that
* are understood by this Logger/Reporter.
* @author Shane_Curcuru@lotus.com
* @return same as {@link java.applet.Applet.getParameterInfo}.
*/
public String[][] getParameterInfo()
{
String pinfo[][] =
{
{ OPT_LOGGERS, "String", "FQCN of Loggers to add" },
{ OPT_LOGFILE, "String",
"Name of file to use for file-based Logger output" },
{ OPT_LOGGINGLEVEL, "int",
"to setLoggingLevel() to control amount of output" },
{ OPT_PERFLOGGING, "boolean",
"if we should log performance data as well" },
{ OPT_INDENT, "int",
"number of spaces to indent for supporting Loggers" },
{ OPT_DEBUG, "boolean", "generic debugging flag" }
};
return pinfo;
}
/**
* Accessor methods for a properties block.
* @return our Properties block.
* @todo should this clone first?
*/
public Properties getProperties()
{
return reporterProps;
}
/**
* Accessor methods for a properties block.
* Always having a Properties block allows users to pass common
* options to a Logger/Reporter without having to know the specific
* 'properties' on the object.
* <p>Much like in Applets, users can call getParameterInfo() to
* find out what kind of properties are available. Callers more
* commonly simply call initialize(p) instead of setProperties(p)</p>
* @author Shane_Curcuru@lotus.com
* @param p Properties to set (should be cloned).
*/
public void setProperties(Properties p)
{
if (p != null)
reporterProps = (Properties) p.clone();
}
/**
* Call once to initialize this Logger/Reporter from Properties.
* <p>Simple hook to allow Logger/Reporters with special output
* items to initialize themselves.</p>
*
* @author Shane_Curcuru@lotus.com
* @param p Properties block to initialize from.
* @param status, true if OK, false if an error occoured.
*/
public boolean initialize(Properties p)
{
setProperties(p);
String dbg = reporterProps.getProperty(OPT_DEBUG);
if ((dbg != null) && dbg.equalsIgnoreCase("true"))
{
setDebug(true);
}
String perf = reporterProps.getProperty(OPT_PERFLOGGING);
if ((perf != null) && perf.equalsIgnoreCase("true"))
{
setPerfLogging(true);
}
// int values need to be parsed
String logLvl = reporterProps.getProperty(OPT_LOGGINGLEVEL);
if (logLvl != null)
{
try
{
setLoggingLevel(Integer.parseInt(logLvl));
}
catch (NumberFormatException numEx)
{ /* no-op */
}
}
// Add however many loggers are askedfor
boolean b = true;
StringTokenizer st =
new StringTokenizer(reporterProps.getProperty(OPT_LOGGERS),
LOGGER_SEPARATOR);
int i;
for (i = 0; st.hasMoreTokens(); i++)
{
String temp = st.nextToken();
if ((temp != null) && (temp.length() > 1))
{
b &= addLogger(temp, reporterProps);
}
}
return true;
}
/**
* Is this Logger/Reporter ready to log results?
* @author Shane_Curcuru@lotus.com
* @return status - true if it's ready to report, false otherwise
* @todo should we check our contained Loggers for their status?
*/
public boolean isReady()
{
return ready;
}
/**
* Flush this Logger/Reporter - should ensure all output is flushed.
* Note that the flush operation is not necessarily pertinent to
* all types of Logger/Reporter - console-type Loggers no-op this.
* @author Shane_Curcuru@lotus.com
*/
public void flush()
{
for (int i = 0; i < numLoggers; i++)
{
loggers[i].flush();
}
}
/**
* Close this Logger/Reporter - should include closing any OutputStreams, etc.
* Logger/Reporters should return isReady() = false after closing.
* @author Shane_Curcuru@lotus.com
*/
public void close()
{
for (int i = 0; i < numLoggers; i++)
{
loggers[i].close();
}
}
/**
* Generic properties for this Reporter.
* <p>Use a Properties block to make it easier to add new features
* and to be able to pass data to our loggers. Any properties that
* we recognize will be set here, and the entire block will be passed
* to any loggers that we control.</p>
*/
protected Properties reporterProps = new Properties();
/**
* This determines the amount of data actually logged out to results.
* <p>Setting this higher will result in more data being logged out.
* Values range from Reporter.CRITICALMSG (0) to TRACEMSG (60).
* For non-performance-critical testing, you may wish to set this high,
* so all data gets logged, and then use reporting tools on the test output
* to filter for human use (since the appropriate level is stored with
* every logMsg() call)</p>
* @see #logMsg(int, java.lang.String)
*/
protected int loggingLevel = DEFAULT_LOGGINGLEVEL;
/**
* Marker that a testcase is currently running.
* <p>NEEDSWORK: should do a better job of reporting results in cases
* where users might not call testCaseInit/testCaseClose in non-nested pairs.</p>
*/
protected boolean duringTestCase = false;
/**
* Flag if we should force loggers closed upon testFileClose.
* <p>Default: true. Standalone tests can leave this alone.
* Test Harnesses may want to reset this so they can have multiple
* file results in one actual output 'file' for file-based loggers.</p>
*/
protected boolean closeOnFileClose = true;
/**
* Accessor method for closeOnFileClose.
*
* @return our value for closeOnFileClose
*/
public boolean getCloseOnFileClose()
{
return closeOnFileClose;
}
/**
* Accessor method for closeOnFileClose.
*
* @param b value to set for closeOnFileClose
*/
public void setCloseOnFileClose(boolean b)
{
closeOnFileClose = b;
}
//-----------------------------------------------------
//-------- Test results computation members and methods --------
//-----------------------------------------------------
/** Name of the current test. */
protected String testName;
/** Description of the current test. */
protected String testComment;
/** Number of current case within a test, usually automatically calculated. */
protected int caseNum;
/** Description of current case within a test. */
protected String caseComment;
/** Overall test result of current test, automatically calculated. */
protected int testResult;
/** Overall test result of current testcase, automatically calculated. */
protected int caseResult;
/**
* Counters for overall number of results - passes, fails, etc.
* @todo update this if we use TestResult objects
*/
protected static final int FILES = 0;
/** NEEDSDOC Field CASES */
protected static final int CASES = 1;
/** NEEDSDOC Field CHECKS */
protected static final int CHECKS = 2;
/** NEEDSDOC Field MAX_COUNTERS */
protected static final int MAX_COUNTERS = CHECKS + 1;
/**
* Counters for overall number of results - passes, fails, etc.
* @todo update this if we use TestResult objects
*/
protected int[] incpCount = new int[MAX_COUNTERS];
/** NEEDSDOC Field passCount */
protected int[] passCount = new int[MAX_COUNTERS];
/** NEEDSDOC Field ambgCount */
protected int[] ambgCount = new int[MAX_COUNTERS];
/** NEEDSDOC Field failCount */
protected int[] failCount = new int[MAX_COUNTERS];
/** NEEDSDOC Field errrCount */
protected int[] errrCount = new int[MAX_COUNTERS];
//-----------------------------------------------------
//-------- Composite Pattern Variables And Methods --------
//-----------------------------------------------------
/**
* Optimization: max number of loggers, stored in an array.
* <p>This is a design decision: normally, you might use a ConsoleReporter,
* some sort of file-based one, and maybe a network-based one.</p>
*/
protected int MAX_LOGGERS = 3;
/**
* Array of loggers to whom we pass results.
* <p>Store our loggers in an array for optimization, since we want
* logging calls to take as little time as possible.</p>
*/
protected Logger[] loggers = new Logger[MAX_LOGGERS];
/** NEEDSDOC Field numLoggers */
protected int numLoggers = 0;
/**
* Add a new Logger to our array, optionally initializing it with Properties.
* <p>Store our Loggers in an array for optimization, since we want
* logging calls to take as little time as possible.</p>
* @todo enable users to add more than MAX_LOGGERS
* @author Gang Of Four
* @param rName fully qualified class name of Logger to add.
* @param p (optional) Properties block to initialize the Logger with.
* @return status - true if successful, false otherwise.
*/
public boolean addLogger(String rName, Properties p)
{
if ((rName == null) || (rName.length() < 1))
return false;
debugPrintln("addLogger(" + numLoggers + ", " + rName + " ...)");
if ((numLoggers + 1) > loggers.length)
{
// @todo enable users to add more than MAX_LOGGERS
return false;
}
// Attempt to add Logger to our list
Class rClass;
Constructor rCtor;
try
{
rClass = Class.forName(rName);
debugPrintln("rClass is " + rClass.toString());
if (p == null)
// @todo should somehow pass along our own props as well
// Need to ensure Reporter and callers of this method always
// coordinate the initialization of the Loggers we hold
{
loggers[numLoggers] = (Logger) rClass.newInstance();
}
else
{
Class[] parameterTypes = new Class[1];
parameterTypes[0] = java.util.Properties.class;
rCtor = rClass.getConstructor(parameterTypes);
Object[] initArgs = new Object[1];
initArgs[0] = (Object) p;
loggers[numLoggers] = (Logger) rCtor.newInstance(initArgs);
}
}
catch (Exception e)
{
// @todo should we inform user why it failed?
// Note: the logMsg may fail since we might not have any reporters at this point!
debugPrintln("addLogger exception: " + e.toString());
logCriticalMsg("addLogger exception: " + e.toString());
logThrowable(CRITICALMSG, e, "addLogger exception:");
return false;
}
// Increment counter for later use
numLoggers++;
return true;
}
/**
* Return an Hashtable of all active Loggers.
* @todo revisit; perhaps use a Vector
* @reurns Hash of all active Loggers; null if none
*
* NEEDSDOC ($objectName$) @return
*/
public Hashtable getLoggers()
{
// Optimization
if (numLoggers == 0)
return (null);
Hashtable temp = new Hashtable();
for (int i = 0; i < numLoggers; i++)
{
temp.put(loggers[i].getClass().getName(), loggers[i]);
}
return temp;
}
/**
* Add the default Logger to this Reporter, whatever it is.
* <p>Only adds the Logger if numLoggers <= 0; if the user has already
* setup another Logger, this is a no-op (for the testwriter who doesn't
* want the performance hit or annoyance of having Console output)</p>
* @author Gang Of Four
* @return status - true if successful, false otherwise.
*/
public boolean addDefaultLogger()
{
// Optimization - return true, since they already have a logger
if (numLoggers > 0)
return true;
return addLogger(DEFAULT_LOGGER, reporterProps);
}
//-----------------------------------------------------
//-------- Testfile / Testcase start and stop routines --------
//-----------------------------------------------------
/**
* Call once to initialize your Loggers for your test file.
* Also resets test name, result, case results, etc.
* <p>Currently, you must init/close your test file before init/closing
* any test cases. No checking is currently done to ensure that
* mismatched test files are not nested. This is an area that needs
* design decisions and some work eventually to be a really clean design.</p>
* <p>Not only do nested testfiles/testcases have implications for good
* testing practices, they may also have implications for various Loggers,
* especially XML or other ones with an implicit hierarcy in the reports.</p>
* @author Shane_Curcuru@lotus.com
* @param name file name or tag specifying the test.
* @param comment comment about the test.
*/
public void testFileInit(String name, String comment)
{
testName = name;
testComment = comment;
testResult = DEFAULT_RESULT;
caseNum = 0;
caseComment = null;
caseResult = DEFAULT_RESULT;
duringTestCase = false;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].testFileInit(testName, testComment);
}
// Log out time whole test script starts
// Note there is a slight delay while logPerfMsg calls all reporters
long t = System.currentTimeMillis();
logPerfMsg(TEST_START, t, testName);
}
/**
* Call once to close out your test and summate results.
* <p>will close an open testCase before closing the file. May also
* force all Loggers closed if getCloseOnFileClose() (which may imply
* that no more output will be logged to file-based reporters)</p>
* @author Shane_Curcuru@lotus.com
* @todo make this settable as to how/where the resultsCounters get output
*/
public void testFileClose()
{
// Cache the time whole test script ends
long t = System.currentTimeMillis();
if (duringTestCase)
{
// Either user messed up (forgot to call testCaseClose) or something went wrong
logErrorMsg("WARNING! testFileClose when duringTestCase=true!");
// Force call to testCaseClose()
testCaseClose();
}
// Actually log the time the test script ends after closing any potentially open testcases
logPerfMsg(TEST_STOP, t, testName);
// Increment our results counters
incrementResultCounter(FILES, testResult);
// Print out an overall count of results by type
// @todo make this settable as to how/where the resultsCounters get output
logResultsCounters();
// end this testfile - finish up any reporting we need to
for (int i = 0; i < numLoggers; i++)
{
// Log we're done and then flush
loggers[i].testFileClose(testComment, resultToString(testResult));
loggers[i].flush();
// Only close each reporter if asked to; this implies we're done
// and can't perform any more logging ourselves (or our reporters)
if (getCloseOnFileClose())
{
loggers[i].close();
}
}
// Note: explicitly leave testResult, caseResult, etc. set for debugging
// purposes or for use by external test harnesses
}
/**
* Implement Logger-only method.
* <p>Here, a Reporter is simply acting as a logger: so don't
* summate any results, do performance measuring, or anything
* else, just pass the call through to our Loggers.
* @param msg message to log out
* @param result result of testfile
*/
public void testFileClose(String msg, String result)
{
if (duringTestCase)
{
// Either user messed up (forgot to call testCaseClose) or something went wrong
logErrorMsg("WARNING! testFileClose when duringTestCase=true!");
// Force call to testCaseClose()
testCaseClose();
}
// end this testfile - finish up any reporting we need to
for (int i = 0; i < numLoggers; i++)
{
// Log we're done and then flush
loggers[i].testFileClose(testComment, resultToString(testResult));
loggers[i].flush();
// Only close each reporter if asked to; this implies we're done
// and can't perform any more logging ourselves (or our reporters)
if (getCloseOnFileClose())
{
loggers[i].close();
}
}
}
/**
* Call once to start each test case; logs out testcase number and your comment.
* <p>Testcase numbers are calculated as integers incrementing from 1. Will
* also close any previously init'd but not closed testcase.</p>
* @author Shane_Curcuru@lotus.com
* @todo investigate tieing this to the actual testCase methodnames,
* instead of blindly incrementing the counter
* @param comment short description of this test case's objective.
*/
public void testCaseInit(String comment)
{
if (duringTestCase)
{
// Either user messed up (forgot to call testCaseClose) or something went wrong
logErrorMsg("WARNING! testCaseInit when duringTestCase=true!");
// Force call to testCaseClose()
testCaseClose();
}
caseNum++;
caseComment = comment;
caseResult = DEFAULT_RESULT;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].testCaseInit(String.valueOf(caseNum) + " "
+ caseComment);
}
duringTestCase = true;
// Note there is a slight delay while logPerfMsg calls all reporters
long t = System.currentTimeMillis();
logPerfMsg(CASE_START, t, caseComment);
}
/**
* Call once to end each test case and sub-summate results.
* @author Shane_Curcuru@lotus.com
*/
public void testCaseClose()
{
long t = System.currentTimeMillis();
logPerfMsg(CASE_STOP, t, caseComment);
if (!duringTestCase)
{
logErrorMsg("WARNING! testCaseClose when duringTestCase=false!");
// Force call to testCaseInit()
// NEEDSWORK: should we really do this? This ensures any results
// are well-formed, however a user might not expect this.
testCaseInit("WARNING! testCaseClose when duringTestCase=false!");
}
duringTestCase = false;
testResult = java.lang.Math.max(testResult, caseResult);
// Increment our results counters
incrementResultCounter(CASES, caseResult);
for (int i = 0; i < numLoggers; i++)
{
loggers[i].testCaseClose(
String.valueOf(caseNum) + " " + caseComment,
resultToString(caseResult));
}
}
/**
* Implement Logger-only method.
* <p>Here, a Reporter is simply acting as a logger: so don't
* summate any results, do performance measuring, or anything
* else, just pass the call through to our Loggers.
* @param msg message of name of test case to log out
* @param result result of testfile
*/
public void testCaseClose(String msg, String result)
{
if (!duringTestCase)
{
logErrorMsg("WARNING! testCaseClose when duringTestCase=false!");
// Force call to testCaseInit()
// NEEDSWORK: should we really do this? This ensures any results
// are well-formed, however a user might not expect this.
testCaseInit("WARNING! testCaseClose when duringTestCase=false!");
}
duringTestCase = false;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].testCaseClose(
String.valueOf(caseNum) + " " + caseComment,
resultToString(caseResult));
}
}
/**
* Calls back into a Test to run test cases in order.
* <p>Use reflection to call back and execute each testCaseXX method
* in the calling test in order, catching exceptions along the way.</p>
* //@todo rename to 'executeTestCases' or something
* //@todo implement options: either an inclusion or exclusion list
* @author Shane Curcuru
* @param testObject the test object itself.
* @param numTestCases number of consecutively numbered test cases to execute.
* @param options (future use: options to pass to testcases)
* @return status, true if OK, false if big bad error occoured
*/
public boolean executeTests(Test testObject, int numTestCases,
Object options)
{
// Flag denoting if we've had any errors
boolean gotException = false;
// Declare all needed java variables
String tmpErrString = "executeTests: no errors yet";
Object noArgs[] = new Object[0]; // use options instead
Class noParams[] = new Class[0];
Method currTestCase;
Class testClass;
// Get class reference for the test applet itself
testClass = testObject.getClass();
logTraceMsg("executeTests: running " + numTestCases + " tests now.");
for (int tcNum = 1; tcNum <= numTestCases; tcNum++)
{
try
{ // get a reference to the next test case that we'll be calling
tmpErrString = "executeTests: No such method: testCase"
+ tcNum + "()";
currTestCase = testClass.getMethod("testCase" + tcNum,
noParams);
// Now directly invoke that test case
tmpErrString =
"executeTests: Method threw an exception: testCase"
+ tcNum + "(): ";
logTraceMsg("executeTests: invoking testCase" + tcNum
+ " now.");
currTestCase.invoke(testObject, noArgs);
}
catch (InvocationTargetException ite)
{
// Catch any error, log it as an error, and allow next test case to run
gotException = true;
testResult = java.lang.Math.max(ERRR_RESULT, testResult);
tmpErrString += ite.toString();
logErrorMsg(tmpErrString);
// Grab the contained error, log it if available
java.lang.Throwable containedThrowable =
ite.getTargetException();
if (containedThrowable != null)
{
logThrowable(ERRORMSG, containedThrowable, tmpErrString + "(1)");
}
logThrowable(ERRORMSG, ite, tmpErrString + "(2)");
} // end of catch
catch (Throwable t)
{
// Catch any error, log it as an error, and allow next test case to run
gotException = true;
testResult = java.lang.Math.max(ERRR_RESULT, testResult);
tmpErrString += t.toString();
logErrorMsg(tmpErrString);
logThrowable(ERRORMSG, t, tmpErrString);
} // end of catch
} // end of for
// Convenience functionality: remind user if they appear to
// have set numTestCases too low
try
{
// Get a reference to the *next* test case after numTestCases
int moreTestCase = numTestCases + 1;
currTestCase = testClass.getMethod("testCase" + moreTestCase, noParams);
// If we get here, we found another testCase - warn the user
logWarningMsg("executeTests: extra testCase"+ moreTestCase
+ " found, perhaps numTestCases is too low?");
}
catch (Throwable t)
{
// Ignore errors: we don't care, since they didn't
// ask us to look for this method anyway
}
// Return true only if everything passed
if (testResult == PASS_RESULT)
return true;
else
return false;
} // end of executeTests
//-----------------------------------------------------
//-------- Test results logging routines --------
//-----------------------------------------------------
/**
* Accessor for loggingLevel, determines what level of log*() calls get output.
* @return loggingLevel, as an int.
*/
public int getLoggingLevel()
{
return loggingLevel;
}
/**
* Accessor for loggingLevel, determines what level of log*() calls get output.
* @param setLL loggingLevel; normalized to be between CRITICALMSG and TRACEMSG.
*/
public void setLoggingLevel(int setLL)
{
if (setLL < CRITICALMSG)
{
loggingLevel = CRITICALMSG;
}
else if (setLL > TRACEMSG)
{
loggingLevel = TRACEMSG;
}
else
{
loggingLevel = setLL;
}
}
/**
* Report a comment to result file with specified severity.
* <p>Works in conjunction with {@link #loggingLevel };
* only outputs messages that are more severe (i.e. lower)
* than the current logging level.</p>
* <p>Note that some Loggers may limit the comment string,
* either in overall length or by stripping any linefeeds, etc.
* This is to allow for optimization of file or database-type
* reporters with fixed fields. Users who need to log out
* special string data should use logArbitrary() instead.</p>
* <p>Remember, use {@link #check(String, String, String)
* various check*() methods} to report the actual results
* of your tests.</p>
* @author Shane_Curcuru@lotus.com
* @param level severity of message.
* @param msg comment to log out.
* @see #loggingLevel
*/
public void logMsg(int level, String msg)
{
if (level > loggingLevel)
return;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].logMsg(level, msg);
}
}
/**
* Report an arbitrary String to result file with specified severity.
* Log out the String provided exactly as-is.
* @author Shane_Curcuru@lotus.com
* @param level severity or class of message.
* @param msg arbitrary String to log out.
*/
public void logArbitrary(int level, String msg)
{
if (level > loggingLevel)
return;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].logArbitrary(level, msg);
}
}
/**
* Logs out statistics to result file with specified severity.
* <p>This is a general-purpose way to log out numeric statistics. We accept
* both a long and a double to allow users to save whatever kind of numbers
* they need to, with the simplest API. The actual meanings of the numbers
* are dependent on the implementer.</p>
* @author Shane_Curcuru@lotus.com
* @param level severity of message.
* @param lVal statistic in long format.
* @param dVal statistic in doubleformat.
* @param msg comment to log out.
*/
public void logStatistic(int level, long lVal, double dVal, String msg)
{
if (level > loggingLevel)
return;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].logStatistic(level, lVal, dVal, msg);
}
}
/**
* Logs out a element to results with specified severity.
* This method is primarily for reporters that output to fixed
* structures, like files, XML data, or databases.
* @author Shane_Curcuru@lotus.com
* @param level severity of message.
* @param element name of enclosing element
* @param attrs hash of name=value attributes
* @param msg Object to log out; up to reporters to handle
* processing of this; usually logs just .toString().
*/
public void logElement(int level, String element, Hashtable attrs,
Object msg)
{
if (level > loggingLevel)
return;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].logElement(level, element, attrs, msg);
}
}
/**
* Logs out Throwable.toString() and a stack trace of the
* Throwable with the specified severity.
* <p>Works in conjuntion with {@link #setLoggingLevel(int)};
* only outputs messages that are more severe than the current
* logging level.</p>
* <p>This uses logArbitrary to log out your msg - message,
* a newline, throwable.toString(), a newline,
* and then throwable.printStackTrace().</p>
* <p>Note that this does not imply a failure or problem in
* a test in any way: many tests may want to verify that
* certain exceptions are thrown, etc.</p>
* @author Shane_Curcuru@lotus.com
* @param level severity of message.
* @param throwable throwable/exception to log out.
* @param msg description of the throwable.
*/
public void logThrowable(int level, Throwable throwable, String msg)
{
if (level > loggingLevel)
return;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].logThrowable(level, throwable, msg);
}
}
/**
* Logs out contents of a Hashtable with specified severity.
* <p>Works in conjuntion with setLoggingLevel(int); only outputs messages that
* are more severe than the current logging level.</p>
* <p>Loggers should store or log the full contents of the hashtable.</p>
* @author Shane_Curcuru@lotus.com
* @param level severity of message.
* @param hash Hashtable to log the contents of.
* @param msg description of the Hashtable.
*/
public void logHashtable(int level, Hashtable hash, String msg)
{
if (level > loggingLevel)
return;
// Don't log anyway if level is 10 or less.
//@todo revisit this decision: I don't like having special
// rules like this to exclude output. On the other hand,
// if the user set loggingLevel this low, they really don't
// want much output coming out, and hashtables are big
if (loggingLevel <= 10)
return;
for (int i = 0; i < numLoggers; i++)
{
loggers[i].logHashtable(level, hash, msg);
}
}
/**
* Logs out an critical a comment to results; always printed out.
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
public void logCriticalMsg(String msg)
{
logMsg(CRITICALMSG, msg);
}
// There is no logFailsOnlyMsg(String msg) method
/**
* Logs out an error a comment to results.
* <p>Note that subclassed libraries may choose to override to
* cause a fail to happen along with printing out the message.</p>
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
public void logErrorMsg(String msg)
{
logMsg(ERRORMSG, msg);
}
/**
* Logs out a warning a comment to results.
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
public void logWarningMsg(String msg)
{
logMsg(WARNINGMSG, msg);
}
/**
* Logs out an status a comment to results.
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
public void logStatusMsg(String msg)
{
logMsg(STATUSMSG, msg);
}
/**
* Logs out an informational a comment to results.
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
public void logInfoMsg(String msg)
{
logMsg(INFOMSG, msg);
}
/**
* Logs out an trace a comment to results.
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
public void logTraceMsg(String msg)
{
logMsg(TRACEMSG, msg);
}
//-----------------------------------------------------
//-------- Test results reporting check* routines --------
//-----------------------------------------------------
// There is no public void checkIncp(String comment) method
/* EXPERIMENTAL: have duplicate set of check*() methods
that all output some form of ID as well as comment.
Leave the non-ID taking forms for both simplicity to the
end user who doesn't care about IDs as well as for
backwards compatibility.
*/
/**
* Writes out a Pass record with comment.
* @author Shane_Curcuru@lotus.com
* @param comment comment to log with the pass record.
*/
public void checkPass(String comment)
{
checkPass(comment, null);
}
/**
* Writes out an ambiguous record with comment.
* @author Shane_Curcuru@lotus.com
* @param comment to log with the ambg record.
*/
public void checkAmbiguous(String comment)
{
checkAmbiguous(comment, null);
}
/**
* Writes out a Fail record with comment.
* @author Shane_Curcuru@lotus.com
* @param comment comment to log with the fail record.
*/
public void checkFail(String comment)
{
checkFail(comment, null);
}
/**
* Writes out an Error record with comment.
* @author Shane_Curcuru@lotus.com
* @param comment comment to log with the error record.
*/
public void checkErr(String comment)
{
checkErr(comment, null);
}
/**
* Writes out a Pass record with comment.
* A Pass signifies that an individual test point has completed and has
* been verified to have behaved correctly.
* <p>If you need to do your own specific comparisons, you can
* do them in your code and then just call checkPass or checkFail.</p>
* <p>Derived classes must implement this to <B>both</B> report the
* results out appropriately <B>and</B> to summate the results, if needed.</p>
* <p>Pass results are a low priority, except for INCP (incomplete). Note
* that if a test never calls check*(), it will have an incomplete result.</p>
* @author Shane_Curcuru@lotus.com
* @param comment to log with the pass record.
* @param ID token to log with the pass record.
*/
public void checkPass(String comment, String id)
{
// Increment our results counters
incrementResultCounter(CHECKS, PASS_RESULT);
// Special: only report it actually if needed
if (getLoggingLevel() > FAILSONLY)
{
for (int i = 0; i < numLoggers; i++)
{
loggers[i].checkPass(comment, id);
}
}
caseResult = java.lang.Math.max(PASS_RESULT, caseResult);
}
/**
* Writes out an ambiguous record with comment.
* <p>Ambiguous results are neither pass nor fail. Different test
* libraries may have slightly different reasons for using ambg.</p>
* <p>Derived classes must implement this to <B>both</B> report the
* results out appropriately <B>and</B> to summate the results, if needed.</p>
* <p>Ambg results have a middling priority, and take precedence over incomplete and pass.</p>
* <p>An Ambiguous result may signify that the test point has completed and either
* appears to have succeded, or that it has produced a result but there is no known
* 'gold' result to compare it to.</p>
* @author Shane_Curcuru@lotus.com
* @param comment to log with the ambg record.
* @param ID token to log with the pass record.
*/
public void checkAmbiguous(String comment, String id)
{
// Increment our results counters
incrementResultCounter(CHECKS, AMBG_RESULT);
for (int i = 0; i < numLoggers; i++)
{
loggers[i].checkAmbiguous(comment, id);
}
caseResult = java.lang.Math.max(AMBG_RESULT, caseResult);
}
/**
* Writes out a Fail record with comment.
* <p>If you need to do your own specific comparisons, you can
* do them in your code and then just call checkPass or checkFail.</p>
* <p>Derived classes must implement this to <B>both</B> report the
* results out appropriately <B>and</B> to summate the results, if needed.</p>
* <p>Fail results have a high priority, and take precedence over incomplete, pass, and ambiguous.</p>
* <p>A Fail signifies that an individual test point has completed and has
* been verified to have behaved <B>in</B>correctly.</p>
* @author Shane_Curcuru@lotus.com
* @param comment to log with the fail record.
* @param ID token to log with the pass record.
*/
public void checkFail(String comment, String id)
{
// Increment our results counters
incrementResultCounter(CHECKS, FAIL_RESULT);
for (int i = 0; i < numLoggers; i++)
{
loggers[i].checkFail(comment, id);
}
caseResult = java.lang.Math.max(FAIL_RESULT, caseResult);
}
/**
* Writes out an Error record with comment.
* <p>Derived classes must implement this to <B>both</B> report the
* results out appropriately <B>and</B> to summate the results, if needed.</p>
* <p>Error results have the highest priority, and take precedence over
* all other results.</p>
* <p>An Error signifies that something unusual has gone wrong with the execution
* of the test at this point - likely something that will require a human to
* debug to see what really happened.</p>
* @author Shane_Curcuru@lotus.com
* @param comment to log with the error record.
* @param ID token to log with the pass record.
*/
public void checkErr(String comment, String id)
{
// Increment our results counters
incrementResultCounter(CHECKS, ERRR_RESULT);
for (int i = 0; i < numLoggers; i++)
{
loggers[i].checkErr(comment, id);
}
caseResult = java.lang.Math.max(ERRR_RESULT, caseResult);
}
//-----------------------------------------------------
//-------- Simplified Performance Logging - beyond interface Reporter --------
//-----------------------------------------------------
/** NEEDSDOC Field DEFAULT_PERFLOGGING_LEVEL */
protected final boolean DEFAULT_PERFLOGGING_LEVEL = false;
/**
* This determines if performance information is logged out to results.
* <p>When true, extra performance records are written out to result files.</p>
* @see #logPerfMsg(java.lang.String, long, java.lang.String)
*/
protected boolean perfLogging = DEFAULT_PERFLOGGING_LEVEL;
/**
* Accessor for perfLogging, determines if we log performance info.
* @todo add PerfLogging to Reporter interface
* @return Whether or not we log performance info.
*/
public boolean getPerfLogging()
{
return (perfLogging);
}
/**
* Accessor for perfLogging, determines if we log performance info.
* @param Whether or not we log performance info.
*
* NEEDSDOC @param setPL
*/
public void setPerfLogging(boolean setPL)
{
perfLogging = setPL;
}
/**
* Constants used to mark performance records in output.
*/
// Note: string representations are explicitly set to all be
// 4 characters long to make it simpler to parse results
public static final String TEST_START = "TSrt";
/** NEEDSDOC Field TEST_STOP */
public static final String TEST_STOP = "TStp";
/** NEEDSDOC Field CASE_START */
public static final String CASE_START = "CSrt";
/** NEEDSDOC Field CASE_STOP */
public static final String CASE_STOP = "CStp";
/** NEEDSDOC Field USER_TIMER */
public static final String USER_TIMER = "UTmr";
/** NEEDSDOC Field USER_TIMESTAMP */
public static final String USER_TIMESTAMP = "UTim";
/** NEEDSDOC Field USER_MEMORY */
public static final String USER_MEMORY = "UMem";
/** NEEDSDOC Field PERF_SEPARATOR */
public static final String PERF_SEPARATOR = ";";
/**
* Logs out a performance statistic.
* <p>Only logs times if perfLogging set to true.</p>
* <p>As an optimization for record-based Loggers, this is a rather simplistic
* way to log performance info - however it's sufficient for most purposes.</p>
* @author Frank Bell
* @param type type of performance statistic.
* @param data long value of performance statistic.
* @param msg comment to log out.
*/
public void logPerfMsg(String type, long data, String msg)
{
if (getPerfLogging())
{
double dummy = 0;
for (int i = 0; i < numLoggers; i++)
{
// NEEDSWORK: simply put it at the current loggingLevel we have set
// Is there a better way to mesh performance output with the rest?
loggers[i].logStatistic(loggingLevel, data, dummy,
type + PERF_SEPARATOR + msg);
}
}
}
/**
* Captures current time in milliseconds, only if perfLogging.
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
protected Hashtable perfTimers = new Hashtable();
/**
* NEEDSDOC Method startTimer
*
*
* NEEDSDOC @param msg
*/
public void startTimer(String msg)
{
// Note optimization: only capture times if perfLogging
if ((perfLogging) && (msg != null))
{
perfTimers.put(msg, new Long(System.currentTimeMillis()));
}
}
/**
* Captures current time in milliseconds and logs out difference.
* Will only log times if perfLogging set to true.
* <p>Only logs time if it finds a corresponding msg entry that was startTimer'd.</p>
* @author Shane_Curcuru@lotus.com
* @param msg comment to log out.
*/
public void stopTimer(String msg)
{
// Capture time immediately to reduce latency
long stopTime = System.currentTimeMillis();
// Note optimization: only use times if perfLogging
if ((perfLogging) && (msg != null))
{
Long startTime = (Long) perfTimers.get(msg);
logPerfMsg(USER_TIMER, (stopTime - startTime.longValue()), msg);
perfTimers.remove(msg);
}
}
/**
* Accessor for currently running test case number, read-only.
* @return current test case number.
*/
public int getCurrentCaseNum()
{
return caseNum;
}
/**
* Accessor for current test case's result, read-only.
* @return current test case result.
*/
public int getCurrentCaseResult()
{
return caseResult;
}
/**
* Accessor for current test case's description, read-only.
* @return current test case result.
*/
public String getCurrentCaseComment()
{
return caseComment;
}
/**
* Accessor for overall test file result, read-only.
* @return test file's overall result.
*/
public int getCurrentFileResult()
{
return testResult;
}
/**
* Utility method to log out overall result counters.
*
* @param count number of this kind of result
* @param desc description of this kind of result
*/
protected void logResultsCounter(int count, String desc)
{
// Optimization: Only log the kinds of results we have
if (count > 0)
logStatistic(loggingLevel, count, 0, desc);
}
/** Utility method to log out overall result counters. */
public void logResultsCounters()
{
// NEEDSWORK: what's the best format to display this stuff in?
// NEEDSWORK: what loggingLevel should we use?
// NEEDSWORK: temporarily skipping the 'files' since
// we only have tests with one file being run
// logResultsCounter(incpCount[FILES], "incpCount[FILES]");
logResultsCounter(incpCount[CASES], "incpCount[CASES]");
logResultsCounter(incpCount[CHECKS], "incpCount[CHECKS]");
// logResultsCounter(passCount[FILES], "passCount[FILES]");
logResultsCounter(passCount[CASES], "passCount[CASES]");
logResultsCounter(passCount[CHECKS], "passCount[CHECKS]");
// logResultsCounter(ambgCount[FILES], "ambgCount[FILES]");
logResultsCounter(ambgCount[CASES], "ambgCount[CASES]");
logResultsCounter(ambgCount[CHECKS], "ambgCount[CHECKS]");
// logResultsCounter(failCount[FILES], "failCount[FILES]");
logResultsCounter(failCount[CASES], "failCount[CASES]");
logResultsCounter(failCount[CHECKS], "failCount[CHECKS]");
// logResultsCounter(errrCount[FILES], "errrCount[FILES]");
logResultsCounter(errrCount[CASES], "errrCount[CASES]");
logResultsCounter(errrCount[CHECKS], "errrCount[CHECKS]");
}
/**
* Utility method to store overall result counters.
*
* @return a Hashtable of various results items suitable for
* passing to logElement as attrs
*/
protected Hashtable createResultsStatusHash()
{
Hashtable resHash = new Hashtable();
if (incpCount[CASES] > 0)
resHash.put(INCP + "-cases", new Integer(incpCount[CASES]));
if (incpCount[CHECKS] > 0)
resHash.put(INCP + "-checks", new Integer(incpCount[CHECKS]));
if (passCount[CASES] > 0)
resHash.put(PASS + "-cases", new Integer(passCount[CASES]));
if (passCount[CHECKS] > 0)
resHash.put(PASS + "-checks", new Integer(passCount[CHECKS]));
if (ambgCount[CASES] > 0)
resHash.put(AMBG + "-cases", new Integer(ambgCount[CASES]));
if (ambgCount[CHECKS] > 0)
resHash.put(AMBG + "-checks", new Integer(ambgCount[CHECKS]));
if (failCount[CASES] > 0)
resHash.put(FAIL + "-cases", new Integer(failCount[CASES]));
if (failCount[CHECKS] > 0)
resHash.put(FAIL + "-checks", new Integer(failCount[CHECKS]));
if (errrCount[CASES] > 0)
resHash.put(ERRR + "-cases", new Integer(errrCount[CASES]));
if (errrCount[CHECKS] > 0)
resHash.put(ERRR + "-checks", new Integer(errrCount[CHECKS]));
return resHash;
}
/**
* Utility method to write out overall result counters.
*
* <p>This writes out both a testsummary element as well as
* writing a separate marker file for the test's currently
* rolled-up test results.</p>
*
* <p>Note if writeFile is true, we do a bunch of additional
* processing, including deleting any potential marker
* files, along with creating a new marker file. This section
* of code explicitly does file creation and also includes
* some basic XML-isms in it.</p>
*
* <p>Marker files look like: [testStat][testName].xml, where
* testStat is the actual current status, like
* Pass/Fail/Ambg/Errr/Incp, and testName comes from the
* currently executing test; this may be overridden by
* setting OPT_SUMMARYFILE.</p>
*
* @param writeFile if we should also write out a separate
* Passname/Failname marker file as well
*/
public void writeResultsStatus(boolean writeFile)
{
final String DEFAULT_SUMMARY_NAME = "ResultsSummary.xml";
Hashtable resultsHash = createResultsStatusHash();
resultsHash.put("desc", testComment);
resultsHash.put("testName", testName);
//@todo the actual path in the property below may not necessarily
// either exist or be the correct location vis-a-vis the file
// that we're writing out - but it should be close
resultsHash.put(OPT_LOGFILE, reporterProps.getProperty(OPT_LOGFILE, DEFAULT_SUMMARY_NAME));
try
{
resultsHash.put("baseref", System.getProperty("user.dir"));
}
catch (Exception e) { /* no-op, ignore */ }
String elementName = "teststatus";
String overallResult = resultToString(getCurrentFileResult());
// Ask each of our loggers to report this
for (int i = 0; i < numLoggers; i++)
{
loggers[i].logElement(CRITICALMSG, elementName, resultsHash, overallResult);
}
// Only continue if user asked us to
if (!writeFile)
return;
// Now write an actual file out as a marker for enclosing
// harnesses and build environments
// Calculate the name relative to any logfile we have
String logFileBase = null;
try
{
// CanonicalPath gives a better path, especially if
// you mix your path separators up
logFileBase = (new File(reporterProps.getProperty(OPT_LOGFILE, DEFAULT_SUMMARY_NAME))).getCanonicalPath();
}
catch (IOException ioe)
{
logFileBase = (new File(reporterProps.getProperty(OPT_LOGFILE, DEFAULT_SUMMARY_NAME))).getAbsolutePath();
}
logFileBase = (new File(logFileBase)).getParent();
// Either use the testName or an optionally set summary name
String summaryFileBase = reporterProps.getProperty(OPT_SUMMARYFILE, testName + ".xml");
final File[] summaryFiles =
{
// Note array is ordered; should be re-designed so this doesn't matter
// Coordinate PASS name with results.marker in build.xml
// File name rationale: put Pass/Fail/etc first, so they
// all show up together in dir listing; include
// testName so you know where it came from; make it
// .xml since it is an XML file
new File(logFileBase, INCP + "-" + summaryFileBase),
new File(logFileBase, PASS + "-" + summaryFileBase),
new File(logFileBase, AMBG + "-" + summaryFileBase),
new File(logFileBase, FAIL + "-" + summaryFileBase),
new File(logFileBase, ERRR + "-" + summaryFileBase)
};
// Clean up any pre-existing files that might be confused
// as markers from this testrun
for (int i = 0; i < summaryFiles.length; i++)
{
if (summaryFiles[i].exists())
summaryFiles[i].delete();
}
File summaryFile = null;
switch (getCurrentFileResult())
{
case INCP_RESULT:
summaryFile = summaryFiles[0];
break;
case PASS_RESULT:
summaryFile = summaryFiles[1];
break;
case AMBG_RESULT:
summaryFile = summaryFiles[2];
break;
case FAIL_RESULT:
summaryFile = summaryFiles[3];
break;
case ERRR_RESULT:
summaryFile = summaryFiles[4];
break;
default:
// Use error case, this should never happen
summaryFile = summaryFiles[4];
break;
}
resultsHash.put(OPT_SUMMARYFILE, summaryFile.getPath());
// Now actually write out the summary file
try
{
PrintWriter printWriter = new PrintWriter(new FileWriter(summaryFile));
// Fake the output of Logger.logElement mostly; except
// we add an XML header so this is a legal XML doc
printWriter.println("<?xml version=\"1.0\"?>");
printWriter.println("<" + elementName);
for (Enumeration keys = resultsHash.keys();
keys.hasMoreElements(); /* no increment portion */ )
{
Object key = keys.nextElement();
printWriter.println(key + "=\"" + resultsHash.get(key) + "\"");
}
printWriter.println(">");
printWriter.println(overallResult);
printWriter.println("</" + elementName + ">");
printWriter.close();
}
catch(Exception e)
{
logErrorMsg("writeResultsStatus: Can't write: " + summaryFile);
}
}
//-----------------------------------------------------
//-------- Test results reporting check* routines --------
//-----------------------------------------------------
/**
* Compares actual and expected, and logs the result, pass/fail.
* The comment you pass is added along with the pass/fail, of course.
* Currenly, you may pass a pair of any of these simple {type}:
* <ui>
* <li>boolean</li>
* <li>byte</li>
* <li>short</li>
* <li>int</li>
* <li>long</li>
* <li>float</li>
* <li>double</li>
* <li>String</li>
* </ui>
* <p>While tests could simply call checkPass(comment), providing these convenience
* method can save lines of code, since you can replace:</p>
* <code>if (foo = bar) <BR>
* checkPass(comment); <BR>
* else <BR>
* checkFail(comment);</code>
* <p>With the much simpler:</p>
* <code>check(foo, bar, comment);</code>
* <p>Plus, you can either use or ignore the boolean return value.</p>
* <p>Note that individual methods checkInt(...), checkLong(...), etc. also exist.
* These type-independent overriden methods are provided as a convenience to
* Java-only testwriters. JavaScript scripts must call the
* type-specific checkInt(...), checkString(...), etc. methods directly.</p>
* <p>Note that testwriters are free to ignore the boolean return value.</p>
* @author Shane_Curcuru@lotus.com
* @param actual value returned from your test code.
* @param expected value that test should return to pass.
* @param comment to log out with result.
* @return status, true=pass, false otherwise
* @see #checkPass
* @see #checkFail
* @see #checkObject
*/
public boolean check(boolean actual, boolean expected, String comment)
{
return (checkBool(actual, expected, comment));
}
/**
* NEEDSDOC Method check
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (check) @return
*/
public boolean check(byte actual, byte expected, String comment)
{
return (checkByte(actual, expected, comment));
}
/**
* NEEDSDOC Method check
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (check) @return
*/
public boolean check(short actual, short expected, String comment)
{
return (checkShort(actual, expected, comment));
}
/**
* NEEDSDOC Method check
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (check) @return
*/
public boolean check(int actual, int expected, String comment)
{
return (checkInt(actual, expected, comment));
}
/**
* NEEDSDOC Method check
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (check) @return
*/
public boolean check(long actual, long expected, String comment)
{
return (checkLong(actual, expected, comment));
}
/**
* NEEDSDOC Method check
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (check) @return
*/
public boolean check(float actual, float expected, String comment)
{
return (checkFloat(actual, expected, comment));
}
/**
* NEEDSDOC Method check
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (check) @return
*/
public boolean check(double actual, double expected, String comment)
{
return (checkDouble(actual, expected, comment));
}
/**
* NEEDSDOC Method check
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (check) @return
*/
public boolean check(String actual, String expected, String comment)
{
return (checkString(actual, expected, comment));
}
// No check(Object, Object, String) currently provided, please call checkObject(...) directly
/**
* Compares actual and expected (Object), and logs the result, pass/fail.
* <p><b>Special note for checkObject:</b></p>
* <p>Since this takes an object reference and not a primitive type,
* it works slightly differently than other check{Type} methods.</p>
* <ui>
* <li>If both are null, then Pass</li>
* <li>Else If actual.equals(expected) than Pass</li>
* <li>Else Fail</li>
* </ui>
* @author Shane_Curcuru@lotus.com
* @param actual Object returned from your test code.
* @param expected Object that test should return to pass.
* @param comment to log out with result.
* @see #checkPass
* @see #checkFail
* @see #check
*
* NEEDSDOC ($objectName$) @return
*/
public boolean checkObject(Object actual, Object expected, String comment)
{
// Pass if both null, or both valid & equals
if (actual != null)
{
if (actual.equals(expected))
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
else
{ // actual is null, so can't use .equals
if (expected == null)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
}
/**
* NEEDSDOC Method checkBool
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (checkBool) @return
*/
public boolean checkBool(boolean actual, boolean expected, String comment)
{
if (actual == expected)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
/**
* NEEDSDOC Method checkByte
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (checkByte) @return
*/
public boolean checkByte(byte actual, byte expected, String comment)
{
if (actual == expected)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
/**
* NEEDSDOC Method checkShort
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (checkShort) @return
*/
public boolean checkShort(short actual, short expected, String comment)
{
if (actual == expected)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
/**
* NEEDSDOC Method checkInt
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (checkInt) @return
*/
public boolean checkInt(int actual, int expected, String comment)
{
if (actual == expected)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
/**
* NEEDSDOC Method checkLong
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (checkLong) @return
*/
public boolean checkLong(long actual, long expected, String comment)
{
if (actual == expected)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
/**
* NEEDSDOC Method checkFloat
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (checkFloat) @return
*/
public boolean checkFloat(float actual, float expected, String comment)
{
if (actual == expected)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
/**
* NEEDSDOC Method checkDouble
*
*
* NEEDSDOC @param actual
* NEEDSDOC @param expected
* NEEDSDOC @param comment
*
* NEEDSDOC (checkDouble) @return
*/
public boolean checkDouble(double actual, double expected, String comment)
{
if (actual == expected)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
/**
* Compares actual and expected (String), and logs the result, pass/fail.
* <p><b>Special note for checkString:</b></p>
* <p>Since this takes a String object and not a primitive type,
* it works slightly differently than other check{Type} methods.</p>
* <ui>
* <li>If both are null, then Pass</li>
* <li>Else If actual.compareTo(expected) == 0 than Pass</li>
* <li>Else Fail</li>
* </ui>
* @author Shane_Curcuru@lotus.com
* @param actual String returned from your test code.
* @param expected String that test should return to pass.
* @param comment to log out with result.
* @see #checkPass
* @see #checkFail
* @see #checkObject
*
* NEEDSDOC ($objectName$) @return
*/
public boolean checkString(String actual, String expected, String comment)
{
// Pass if both null, or both valid & equals
if (actual != null)
{
// .compareTo returns 0 if the strings match lexicographically
if ((expected != null) && (actual.compareTo(expected) == 0))
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
else
{ // actual is null, so can't use .equals
if (expected == null)
{
checkPass(comment);
return true;
}
else
{
checkFail(comment);
return false;
}
}
}
/**
* Uses an external CheckService to Compares actual and expected,
* and logs the result, pass/fail.
* <p>CheckServices may be implemented to do custom equivalency
* checking between complex object types. It is the responsibility
* of the CheckService to call back into us to report results.</p>
* @author Shane_Curcuru@lotus.com
* @param CheckService implementation to use
*
* @param service a non-null CheckService implementation for
* this type of actual and expected object
* @param actual Object returned from your test code.
* @param expected Object that test should return to pass.
* @param comment to log out with result.
* @return status true if PASS_RESULT, false otherwise
* @see #checkPass
* @see #checkFail
* @see #check
*/
public boolean check(CheckService service, Object actual,
Object expected, String comment)
{
if (service == null)
{
checkErr("CheckService null for: " + comment);
return false;
}
if (service.check(this, actual, expected, comment) == PASS_RESULT)
return true;
else
return false;
}
/**
* Uses an external CheckService to Compares actual and expected,
* and logs the result, pass/fail.
*/
public boolean check(CheckService service, Object actual,
Object expected, String comment, String id)
{
if (service == null)
{
checkErr("CheckService null for: " + comment);
return false;
}
if (service.check(this, actual, expected, comment, id) == PASS_RESULT)
return true;
else
return false;
}
/** Flag to control internal debugging of Reporter; sends extra info to System.out. */
protected boolean debug = false;
/**
* Accessor for internal debugging flag.
*
* NEEDSDOC ($objectName$) @return
*/
public boolean getDebug()
{
return (debug);
}
/**
* Accessor for internal debugging flag.
*
* NEEDSDOC @param setDbg
*/
public void setDebug(boolean setDbg)
{
debug = setDbg;
debugPrintln("setDebug enabled"); // will only print if setDbg was true
}
/**
* Basic debugging output wrapper for Reporter.
*
* NEEDSDOC @param msg
*/
public void debugPrintln(String msg)
{
if (!debug)
return;
// If we have reporters, use them
if (numLoggers > 0)
logCriticalMsg("RI.dP: " + msg);
// Otherwise, just dump to the console
else
System.out.println("RI.dP: " + msg);
}
/**
* Utility method to increment result counters.
*
* NEEDSDOC @param ctrOffset
* NEEDSDOC @param r
*/
public void incrementResultCounter(int ctrOffset, int r)
{
switch (r)
{
case INCP_RESULT :
incpCount[ctrOffset]++;
break;
case PASS_RESULT :
passCount[ctrOffset]++;
break;
case AMBG_RESULT :
ambgCount[ctrOffset]++;
break;
case FAIL_RESULT :
failCount[ctrOffset]++;
break;
case ERRR_RESULT :
errrCount[ctrOffset]++;
break;
default :
; // NEEDSWORK: should we report this, or allow users to add their own counters?
}
}
/**
* Utility method to translate an int result to a string.
*
* NEEDSDOC @param r
*
* NEEDSDOC ($objectName$) @return
*/
public static String resultToString(int r)
{
switch (r)
{
case INCP_RESULT :
return (INCP);
case PASS_RESULT :
return (PASS);
case AMBG_RESULT :
return (AMBG);
case FAIL_RESULT :
return (FAIL);
case ERRR_RESULT :
return (ERRR);
default :
return ("Unkn"); // NEEDSWORK: should have better constant for this
}
}
} // end of class Reporter