• 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 package org.apache.qetest;
23 
24 import java.io.BufferedReader;
25 import java.io.File;
26 import java.io.InputStreamReader;
27 import java.lang.reflect.Method;
28 import java.util.Hashtable;
29 
30 /**
31  * Base class for testing commandline driven products.
32  *
33  * This class provides a default algorithim for testing any
34  * command line based tool.  Subclasses define the
35  * exact command line args, etc. used for different products.
36  * Subclasses can also either shell an external process or can
37  * just construct a class and call main().
38  *
39  * @author Shane_Curcuru@us.ibm.com
40  * @version $Id$
41  */
42 public abstract class ExecTestlet extends FileTestlet
43 {
44     /**
45      * Parameter: Actual name of external program to call.
46      */
47     public static final String OPT_PROGNAME = "progName";
48 
49     /**
50      * Timing data: how long process takes to exec.
51      * Default is -1 to represent a bogus number.
52      */
53     protected long timeExec = -1;
54 
55     /**
56      * Default path/name of external program to call, OR
57      * actual name of class to call.
58      * @return foo, must be overridden.
59      */
getProgram()60     public abstract String getProgram();
61 
62     /**
63      * If the program should be shelled out or if it is a Java
64      * class to call main on.
65      * @return foo, must be overridden.
66      */
isExternal()67     public abstract boolean isExternal();
68 
69     /**
70      * Worker method to get list of arguments specific to this program.
71      *
72      * <p>Should construct whole list of arguments needed to call
73      * this program, including any options and args needed to
74      * process the files in the datalet.  Must be overriden.</p>
75      *
76      * <p>If isExternal is true, this should presumably put the
77      * name of the program first, since we just shell that as a
78      * command line.  If isExternal is false, this should <b>not</b>
79      * include the Java classname.</p>
80      *
81      * @param datalet that defined the test data
82      * @return String array of arguments suitable to pass to
83      * Runtime.exec() or main()
84      */
getArguments(FileDatalet datalet)85     public abstract String[] getArguments(FileDatalet datalet);
86 
87     /**
88      * Worker method to actually perform the test;
89      * overriden to use command line processing.
90      *
91      * Logs out applicable info; attempts to perform transformation.
92      *
93      * @param datalet to test with
94      * @throws allows any underlying exception to be thrown
95      */
testDatalet(FileDatalet datalet)96     protected void testDatalet(FileDatalet datalet)
97             throws Exception
98     {
99         String[] args = getArguments(datalet);
100 
101         StringBuffer argBuf = new StringBuffer();
102         for (int i = 0; i < args.length; i++)
103         {
104             argBuf.append(args[i]);
105             argBuf.append(" ");
106         }
107         logger.logMsg(Logger.TRACEMSG, "testDatalet executing: " + argBuf.toString());
108 
109         // Use one of two worker methods to execute the process, either
110         //  by shelling an external process, or by constructing the
111         //  Java object and then calling main()
112         if (isExternal())
113             execProcess(datalet, args);
114         else
115             execMain(datalet, args);
116     }
117 
118     /**
119      * Worker method to call a Java class' main() method.
120      *
121      * <p>Simply calls a no-arg constructor and then passes the
122      * args to the main() method.  May be overridden.</p>
123      *
124      * @param datalet that defined the test data
125      * @param cmdline actual command line to run, including program name
126      * @param environment passed as-is to Process.run
127      * @return return value from program
128      * @exception Exception may be thrown by Runtime.exec
129      */
execMain(FileDatalet datalet, String[] cmdline)130     public void execMain(FileDatalet datalet, String[] cmdline)
131             throws Exception
132     {
133         // Default implementation; may be overriden
134         Class clazz = Class.forName(getProgram());
135         if (null == clazz)
136         {
137             logger.checkErr("Can't find classname: " + getProgram());
138             return;
139         }
140 
141         try
142         {
143             // ...find the main() method...
144             Class[] parameterTypes = new Class[1];
145             parameterTypes[0] = java.lang.String[].class;
146             Method main = clazz.getMethod("main", parameterTypes);
147 
148             // ...and execute the method!
149             Object[] mainArgs = new Object[1];
150             mainArgs[0] = cmdline;
151             final long startTime = System.currentTimeMillis();
152             main.invoke(null, mainArgs);
153             timeExec = System.currentTimeMillis() - startTime;
154 
155             // Also log out a perf element by default
156             Hashtable attrs = new Hashtable();
157             attrs.put("program", getProgram());
158             attrs.put("isExternal", "false");
159             attrs.put("timeExec", new Long(timeExec));
160             logPerf(datalet, attrs);
161             attrs = null;
162         }
163         catch (Throwable t)
164         {
165             logger.logThrowable(Logger.ERRORMSG, t, "Javaclass.main() threw");
166             logger.checkErr(getProgram() + ".main() threw: " + t.toString());
167         }
168     }
169 
170     /**
171      * Worker method to shell out an external process.
172      *
173      * <p>Does a simple capturing of the out and err streams from
174      * the process and logs them out.  Inherits the same environment
175      * that the current JVM is in.  No need to override</p>
176      *
177      * @param datalet that defined the test data
178      * @param cmdline actual command line to run, including program name
179      * @param environment passed as-is to Process.run
180      * @return return value from program
181      * @exception Exception may be thrown by Runtime.exec
182      */
execProcess(FileDatalet datalet, String[] cmdline)183     public void execProcess(FileDatalet datalet, String[] cmdline)
184             throws Exception
185     {
186         if ((cmdline == null) || (cmdline.length < 1))
187         {
188             logger.checkFail("execProcess called with null/blank arguments!");
189             return;
190         }
191 
192         int bufSize = 2048; // Arbitrary bufSize seems to work well
193         ThreadedStreamReader outReader = new ThreadedStreamReader();
194         ThreadedStreamReader errReader = new ThreadedStreamReader();
195         Runtime r = Runtime.getRuntime();
196         java.lang.Process proc = null;
197 
198         // Actually begin executing the program
199         logger.logMsg(Logger.TRACEMSG, "execProcess starting " + cmdline[0]);
200 
201         //@todo Note: we should really provide a way for the datalet
202         //  to specify any additional environment needed for the
203         //  second arg to exec();
204         String[] environment = null;
205         final long startTime = System.currentTimeMillis();
206         proc = r.exec(cmdline, environment);
207 
208         // Immediately begin capturing any output therefrom
209         outReader.setInputStream(
210             new BufferedReader(
211                 new InputStreamReader(proc.getInputStream()), bufSize));
212         errReader.setInputStream(
213             new BufferedReader(
214                 new InputStreamReader(proc.getErrorStream()), bufSize));
215 
216         // Start two threads off on reading the System.out and System.err from proc
217         outReader.start();
218         errReader.start();
219         int processReturnVal = -2; // HACK the default
220         try
221         {
222             // Wait for the process to exit normally
223             processReturnVal = proc.waitFor();
224             // Record time we finally rejoin, i.e. when the process is done
225             timeExec = System.currentTimeMillis() - startTime;
226         }
227         catch (InterruptedException ie1)
228         {
229             logger.logThrowable(Logger.ERRORMSG, ie1,
230                                   "execProcess proc.waitFor() threw");
231         }
232 
233         // Now that we're done, presumably the Readers are also done
234         StringBuffer sysOut = null;
235         StringBuffer sysErr = null;
236         try
237         {
238             outReader.join();
239             sysOut = outReader.getBuffer();
240         }
241         catch (InterruptedException ie2)
242         {
243             logger.logThrowable(Logger.ERRORMSG, ie2, "Joining outReader threw");
244         }
245 
246         try
247         {
248             errReader.join();
249             sysErr = errReader.getBuffer();
250         }
251         catch (InterruptedException ie3)
252         {
253             logger.logThrowable(Logger.ERRORMSG, ie3, "Joining errReader threw");
254         }
255 
256         logAndCheckStreams(datalet, cmdline, sysOut, sysErr, processReturnVal);
257     }
258 
259 
260     /**
261      * Worker method to evaluate the System.out/.err streams of
262      * a particular processor.
263      *
264      * Logs out the streams if available, then calls a worker method
265      * to actually call check() if specific validation needed.
266      *
267      * @param datalet that defined the test data
268      * @param cmdline that was used for execProcess
269      * @param outBuf buffer from execProcess' System.out
270      * @param errBuf buffer from execProcess' System.err
271      * @param processReturnVal from execProcess
272      */
logAndCheckStreams(FileDatalet datalet, String[] cmdline, StringBuffer outBuf, StringBuffer errBuf, int processReturnVal)273     protected void logAndCheckStreams(FileDatalet datalet, String[] cmdline,
274             StringBuffer outBuf, StringBuffer errBuf, int processReturnVal)
275     {
276         Hashtable attrs = new Hashtable();
277         attrs.put("program", cmdline[0]);
278         attrs.put("returnVal", String.valueOf(processReturnVal));
279 
280         StringBuffer buf = new StringBuffer();
281         if ((null != errBuf) && (errBuf.length() > 0))
282         {
283             buf.append("<system-err>");
284             buf.append(errBuf);
285             buf.append("</system-err>\n");
286         }
287         if ((null != outBuf) && (outBuf.length() > 0))
288         {
289             buf.append("<system-out>");
290             buf.append(outBuf);
291             buf.append("</system-out>\n");
292         }
293         logger.logElement(Logger.INFOMSG, "checkOutputStreams", attrs, buf.toString());
294         buf = null;
295 
296         // Also log out a perf element by default
297         attrs = new Hashtable();
298         attrs.put("program", cmdline[0]);
299         attrs.put("isExternal", "true");
300         attrs.put("timeExec", new Long(timeExec));
301         logPerf(datalet, attrs);
302         attrs = null;
303 
304         // Also call worker method to allow subclasses to
305         //  override checking of the output streams, as available
306         checkStreams(datalet, cmdline, outBuf, errBuf, processReturnVal);
307     }
308 
309 
310     /**
311      * Worker method to validate the System.out/.err streams.
312      *
313      * Default implementation does nothing; override if you wish
314      * to actually validate the specific streams.
315      *
316      * @param datalet that defined the test data
317      * @param cmdline that was used for execProcess
318      * @param outBuf buffer from execProcess' System.out
319      * @param errBuf buffer from execProcess' System.err
320      * @param processReturnVal from execProcess
321      */
checkStreams(FileDatalet datalet, String[] cmdline, StringBuffer outBuf, StringBuffer errBuf, int processReturnVal)322     protected void checkStreams(FileDatalet datalet, String[] cmdline,
323             StringBuffer outBuf, StringBuffer errBuf, int processReturnVal)
324     {
325         // Default impl is no-op
326         return;
327     }
328 
329 
330     /**
331      * Worker method to write performance data in standard format.
332      *
333      * Writes out a perf elem with standardized idref, testlet,
334      * input/output, and fileSize params.
335      *
336      * @param datalet to use for idref, etc.
337      * @param hash of extra attributes to log.
338      */
logPerf(FileDatalet datalet, Hashtable hash)339     protected void logPerf(FileDatalet datalet, Hashtable hash)
340     {
341         if (null == hash)
342             hash = new Hashtable();
343 
344         File f = new File(datalet.getInput());
345 
346         hash.put("idref", f.getName());
347         hash.put("input", datalet.getInput());
348         hash.put("output", datalet.getOutput());
349         hash.put("testlet", thisClassName);
350         try
351         {
352             // Attempt to store size of input file, since overall
353             //  amount of data affects performance
354             hash.put("fileSize", new Long(f.length()));
355         }
356         catch (Exception e)
357         {
358             hash.put("fileSize", "threw: " + e.toString());
359         }
360 
361         logger.logElement(Logger.STATUSMSG, "perf", hash, getCheckDescription(datalet));
362     }
363 
364 }  // end of class ExecTestlet
365 
366