/* * 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. * *
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.
* *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 not * include the Java classname.
* * @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. * *Simply calls a no-arg constructor and then passes the * args to the main() method. May be overridden.
* * @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. * *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
* * @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("