blob: 728e7ddf95b087caa030fc0f458d755bb3fba5ae [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$
*/
package org.apache.qetest;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Hashtable;
/**
* Base class for testing commandline driven products.
*
* This class provides a default algorithim for testing any
* command line based tool. Subclasses define the
* exact command line args, etc. used for different products.
* Subclasses can also either shell an external process or can
* just construct a class and call main().
*
* @author Shane_Curcuru@us.ibm.com
* @version $Id$
*/
public abstract class ExecTestlet extends FileTestlet
{
/**
* Parameter: Actual name of external program to call.
*/
public static final String OPT_PROGNAME = "progName";
/**
* Timing data: how long process takes to exec.
* Default is -1 to represent a bogus number.
*/
protected long timeExec = -1;
/**
* Default path/name of external program to call, OR
* actual name of class to call.
* @return foo, must be overridden.
*/
public abstract String getProgram();
/**
* If the program should be shelled out or if it is a Java
* class to call main on.
* @return foo, must be overridden.
*/
public abstract boolean isExternal();
/**
* Worker method to get list of arguments specific to this program.
*
* <p>Should construct whole list of arguments needed to call
* this program, including any options and args needed to
* process the files in the datalet. Must be overriden.</p>
*
* <p>If isExternal is true, this should presumably put the
* name of the program first, since we just shell that as a
* command line. If isExternal is false, this should <b>not</b>
* include the Java classname.</p>
*
* @param datalet that defined the test data
* @return String array of arguments suitable to pass to
* Runtime.exec() or main()
*/
public abstract String[] getArguments(FileDatalet datalet);
/**
* Worker method to actually perform the test;
* overriden to use command line processing.
*
* Logs out applicable info; attempts to perform transformation.
*
* @param datalet to test with
* @throws allows any underlying exception to be thrown
*/
protected void testDatalet(FileDatalet datalet)
throws Exception
{
String[] args = getArguments(datalet);
StringBuffer argBuf = new StringBuffer();
for (int i = 0; i < args.length; i++)
{
argBuf.append(args[i]);
argBuf.append(" ");
}
logger.logMsg(Logger.TRACEMSG, "testDatalet executing: " + argBuf.toString());
// Use one of two worker methods to execute the process, either
// by shelling an external process, or by constructing the
// Java object and then calling main()
if (isExternal())
execProcess(datalet, args);
else
execMain(datalet, args);
}
/**
* Worker method to call a Java class' main() method.
*
* <p>Simply calls a no-arg constructor and then passes the
* args to the main() method. May be overridden.</p>
*
* @param datalet that defined the test data
* @param cmdline actual command line to run, including program name
* @param environment passed as-is to Process.run
* @return return value from program
* @exception Exception may be thrown by Runtime.exec
*/
public void execMain(FileDatalet datalet, String[] cmdline)
throws Exception
{
// Default implementation; may be overriden
Class clazz = Class.forName(getProgram());
if (null == clazz)
{
logger.checkErr("Can't find classname: " + getProgram());
return;
}
try
{
// ...find the main() method...
Class[] parameterTypes = new Class[1];
parameterTypes[0] = java.lang.String[].class;
Method main = clazz.getMethod("main", parameterTypes);
// ...and execute the method!
Object[] mainArgs = new Object[1];
mainArgs[0] = cmdline;
final long startTime = System.currentTimeMillis();
main.invoke(null, mainArgs);
timeExec = System.currentTimeMillis() - startTime;
// Also log out a perf element by default
Hashtable attrs = new Hashtable();
attrs.put("program", getProgram());
attrs.put("isExternal", "false");
attrs.put("timeExec", new Long(timeExec));
logPerf(datalet, attrs);
attrs = null;
}
catch (Throwable t)
{
logger.logThrowable(Logger.ERRORMSG, t, "Javaclass.main() threw");
logger.checkErr(getProgram() + ".main() threw: " + t.toString());
}
}
/**
* Worker method to shell out an external process.
*
* <p>Does a simple capturing of the out and err streams from
* the process and logs them out. Inherits the same environment
* that the current JVM is in. No need to override</p>
*
* @param datalet that defined the test data
* @param cmdline actual command line to run, including program name
* @param environment passed as-is to Process.run
* @return return value from program
* @exception Exception may be thrown by Runtime.exec
*/
public void execProcess(FileDatalet datalet, String[] cmdline)
throws Exception
{
if ((cmdline == null) || (cmdline.length < 1))
{
logger.checkFail("execProcess called with null/blank arguments!");
return;
}
int bufSize = 2048; // Arbitrary bufSize seems to work well
ThreadedStreamReader outReader = new ThreadedStreamReader();
ThreadedStreamReader errReader = new ThreadedStreamReader();
Runtime r = Runtime.getRuntime();
java.lang.Process proc = null;
// Actually begin executing the program
logger.logMsg(Logger.TRACEMSG, "execProcess starting " + cmdline[0]);
//@todo Note: we should really provide a way for the datalet
// to specify any additional environment needed for the
// second arg to exec();
String[] environment = null;
final long startTime = System.currentTimeMillis();
proc = r.exec(cmdline, environment);
// Immediately begin capturing any output therefrom
outReader.setInputStream(
new BufferedReader(
new InputStreamReader(proc.getInputStream()), bufSize));
errReader.setInputStream(
new BufferedReader(
new InputStreamReader(proc.getErrorStream()), bufSize));
// Start two threads off on reading the System.out and System.err from proc
outReader.start();
errReader.start();
int processReturnVal = -2; // HACK the default
try
{
// Wait for the process to exit normally
processReturnVal = proc.waitFor();
// Record time we finally rejoin, i.e. when the process is done
timeExec = System.currentTimeMillis() - startTime;
}
catch (InterruptedException ie1)
{
logger.logThrowable(Logger.ERRORMSG, ie1,
"execProcess proc.waitFor() threw");
}
// Now that we're done, presumably the Readers are also done
StringBuffer sysOut = null;
StringBuffer sysErr = null;
try
{
outReader.join();
sysOut = outReader.getBuffer();
}
catch (InterruptedException ie2)
{
logger.logThrowable(Logger.ERRORMSG, ie2, "Joining outReader threw");
}
try
{
errReader.join();
sysErr = errReader.getBuffer();
}
catch (InterruptedException ie3)
{
logger.logThrowable(Logger.ERRORMSG, ie3, "Joining errReader threw");
}
logAndCheckStreams(datalet, cmdline, sysOut, sysErr, processReturnVal);
}
/**
* Worker method to evaluate the System.out/.err streams of
* a particular processor.
*
* Logs out the streams if available, then calls a worker method
* to actually call check() if specific validation needed.
*
* @param datalet that defined the test data
* @param cmdline that was used for execProcess
* @param outBuf buffer from execProcess' System.out
* @param errBuf buffer from execProcess' System.err
* @param processReturnVal from execProcess
*/
protected void logAndCheckStreams(FileDatalet datalet, String[] cmdline,
StringBuffer outBuf, StringBuffer errBuf, int processReturnVal)
{
Hashtable attrs = new Hashtable();
attrs.put("program", cmdline[0]);
attrs.put("returnVal", String.valueOf(processReturnVal));
StringBuffer buf = new StringBuffer();
if ((null != errBuf) && (errBuf.length() > 0))
{
buf.append("<system-err>");
buf.append(errBuf);
buf.append("</system-err>\n");
}
if ((null != outBuf) && (outBuf.length() > 0))
{
buf.append("<system-out>");
buf.append(outBuf);
buf.append("</system-out>\n");
}
logger.logElement(Logger.INFOMSG, "checkOutputStreams", attrs, buf.toString());
buf = null;
// Also log out a perf element by default
attrs = new Hashtable();
attrs.put("program", cmdline[0]);
attrs.put("isExternal", "true");
attrs.put("timeExec", new Long(timeExec));
logPerf(datalet, attrs);
attrs = null;
// Also call worker method to allow subclasses to
// override checking of the output streams, as available
checkStreams(datalet, cmdline, outBuf, errBuf, processReturnVal);
}
/**
* Worker method to validate the System.out/.err streams.
*
* Default implementation does nothing; override if you wish
* to actually validate the specific streams.
*
* @param datalet that defined the test data
* @param cmdline that was used for execProcess
* @param outBuf buffer from execProcess' System.out
* @param errBuf buffer from execProcess' System.err
* @param processReturnVal from execProcess
*/
protected void checkStreams(FileDatalet datalet, String[] cmdline,
StringBuffer outBuf, StringBuffer errBuf, int processReturnVal)
{
// Default impl is no-op
return;
}
/**
* Worker method to write performance data in standard format.
*
* Writes out a perf elem with standardized idref, testlet,
* input/output, and fileSize params.
*
* @param datalet to use for idref, etc.
* @param hash of extra attributes to log.
*/
protected void logPerf(FileDatalet datalet, Hashtable hash)
{
if (null == hash)
hash = new Hashtable();
File f = new File(datalet.getInput());
hash.put("idref", f.getName());
hash.put("input", datalet.getInput());
hash.put("output", datalet.getOutput());
hash.put("testlet", thisClassName);
try
{
// Attempt to store size of input file, since overall
// amount of data affects performance
hash.put("fileSize", new Long(f.length()));
}
catch (Exception e)
{
hash.put("fileSize", "threw: " + e.toString());
}
logger.logElement(Logger.STATUSMSG, "perf", hash, getCheckDescription(datalet));
}
} // end of class ExecTestlet